summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Prykop <jonathan@freeside.biz>2015-10-20 20:56:13 -0500
committerMitch Jackson <mitch@freeside.biz>2018-10-09 12:20:05 -0400
commit6d42808bbd3ac9c840c6259aef1dc79a0dc620ff (patch)
treecdcdeeb07f00b43774408e43930c06c398cf7f42
parent61d5de1214428faa8bae0a50653ce8dff8d98ad8 (diff)
RT#38217: Send email when logging conditions are met
-rw-r--r--FS/FS/Schema.pm19
-rw-r--r--FS/FS/log.pm35
-rw-r--r--FS/FS/log_email.pm108
-rw-r--r--FS/FS/msg_template.pm6
-rw-r--r--httemplate/browse/log_email.html92
-rw-r--r--httemplate/edit/log_email.html45
-rw-r--r--httemplate/edit/process/log_email.html18
-rw-r--r--httemplate/elements/menu.html4
-rw-r--r--httemplate/elements/tr-select-msg_template.html12
-rw-r--r--httemplate/misc/delete-log_email.html20
-rw-r--r--httemplate/search/log.html4
11 files changed, 359 insertions, 4 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 447a6bc..ecd071e 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -4658,6 +4658,25 @@ sub tables_hashref {
'index' => [],
},
+ 'log_email' => {
+ 'columns' => [
+ 'logemailnum', 'serial', '', '', '', '',
+ 'context', 'varchar', 'NULL', $char_d, '', '',
+ 'min_level', 'int', 'NULL', '', '', '',
+ 'msgnum', 'int', '', '', '', '',
+ 'to_addr', 'varchar', 'NULL', 255, '', '',
+ ],
+ 'primary_key' => 'logemailnum',
+ 'unique' => [],
+ 'index' => [ ['context'], ['min_level'] ],
+ 'foreign_keys' => [
+ { columns => [ 'msgnum' ],
+ table => 'msg_template',
+ references => [ 'msgnum' ],
+ },
+ ],
+ },
+
'svc_alarm' => {
'columns' => [
# name type null length default local
diff --git a/FS/FS/log.pm b/FS/FS/log.pm
index 547beb7..18ba7c9 100644
--- a/FS/FS/log.pm
+++ b/FS/FS/log.pm
@@ -5,6 +5,7 @@ use base qw( FS::Record );
use FS::Record qw( qsearch qsearchs dbdef );
use FS::UID qw( dbh driver_name );
use FS::log_context;
+use FS::log_email;
=head1 NAME
@@ -71,6 +72,8 @@ otherwise returns false.
CONTEXT may be a list of context tags to attach to this record.
+Will send emails according to the conditions in L<FS::log_email>.
+
=cut
sub insert {
@@ -78,6 +81,7 @@ sub insert {
my $self = shift;
my $error = $self->SUPER::insert;
return $error if $error;
+ my $contexts = {}; #for quick checks when sending emails
foreach ( @_ ) {
my $context = FS::log_context->new({
'lognum' => $self->lognum,
@@ -85,11 +89,40 @@ sub insert {
});
$error = $context->insert;
return $error if $error;
+ $contexts->{$_} = 1;
+ }
+ foreach my $log_email (
+ qsearch('log_email',
+ {
+ 'disabled' => '',
+ 'min_level' => {
+ 'op' => '<=',
+ 'value' => $self->level,
+ },
+ }
+ )
+ ) {
+ # shouldn't be a lot of these, so not packing this into the qsearch
+ next if $log_email->context && !$contexts->{$log_email->context};
+ my $msg_template = qsearchs('msg_template',{ 'msgnum' => $log_email->msgnum });
+ unless ($msg_template) {
+ warn "Could not send email when logging, could not load message template for logemailnum " . $log_email->logemailnum;
+ next;
+ }
+ my $emailerror = $msg_template->send(
+ 'to' => $log_email->to_addr,
+ 'substitutions' => {
+ 'loglevel' => $FS::Log::LEVELS[$self->level], # which has hopefully been loaded...
+ 'logcontext' => $log_email->context, # use the one that triggered the email
+ 'logmessage' => $self->message,
+ },
+ );
+ warn "Could not send email when logging: $emailerror" if $emailerror;
}
'';
}
-# the insert method can be inherited from FS::Record
+# these methods can be inherited from FS::Record
sub delete { die "Log entries can't be modified." };
diff --git a/FS/FS/log_email.pm b/FS/FS/log_email.pm
new file mode 100644
index 0000000..9c53c23
--- /dev/null
+++ b/FS/FS/log_email.pm
@@ -0,0 +1,108 @@
+package FS::log_email;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs dbdef );
+use FS::UID qw( dbh driver_name );
+
+=head1 NAME
+
+FS::log_email - Object methods for log email records
+
+=head1 SYNOPSIS
+
+ use FS::log_email;
+
+ $record = new FS::log_email \%hash;
+ $record = new FS::log_email { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::log object represents the conditions for sending an email
+when a log entry is created. FS::log inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item logemailnum - primary key
+
+=item context - the context that will trigger the email (all contexts if unspecified)
+
+=item min_level - the minimum log level that will trigger the email (all levels if unspecified)
+
+=item msgnum - the msg_template that will be used to send the email
+
+=item to_addr - who the email will be sent to (in addition to any bcc on the template)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new log_email entry.
+
+=cut
+
+sub table { 'log_email'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('logemailnum')
+ || $self->ut_textn('context') # not validating against list of contexts in log_context,
+ # because not even log_context check currently does so
+ || $self->ut_number('min_level')
+ || $self->ut_foreign_key('msgnum', 'msg_template', 'msgnum')
+ || $self->ut_textn('to_addr')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm
index 50a9b3f..2388aba 100644
--- a/FS/FS/msg_template.pm
+++ b/FS/FS/msg_template.pm
@@ -239,7 +239,7 @@ Options are passed as a list of name/value pairs:
=item cust_main
-Customer object (required).
+Customer object
=item object
@@ -277,8 +277,8 @@ A hash reference of additional substitutions
sub prepare {
my( $self, %opt ) = @_;
- my $cust_main = $opt{'cust_main'} or die 'cust_main required';
- my $object = $opt{'object'} or die 'object required';
+ my $cust_main = $opt{'cust_main'}; # or die 'cust_main required';
+ my $object = $opt{'object'}; # or die 'object required';
# localization
my $locale = $cust_main->locale || '';
diff --git a/httemplate/browse/log_email.html b/httemplate/browse/log_email.html
new file mode 100644
index 0000000..0f64dd4
--- /dev/null
+++ b/httemplate/browse/log_email.html
@@ -0,0 +1,92 @@
+<% include('/elements/init_overlib.html') %>
+<% include('/browse/elements/browse.html',
+ 'title' => 'Log email condition configuration',
+ 'name_singular' => 'condition',
+ 'html_init' => '<P STYLE="margin-top: 0">'
+ . $add_condition_link
+ . ' | '
+ . $system_log_link
+ . '</P>'
+ . '<SCRIPT>'
+ . $areyousure
+ . '</SCRIPT>',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Context',
+ 'Min. Level',
+ 'Template',
+ 'To',
+ '',
+ ],
+ 'fields' => [ 'logemailnum',
+ sub { $_[0]->context || '(all)' },
+ sub { $FS::Log::LEVELS[$_[0]->min_level] },
+ 'msgname',
+ 'to_addr',
+ $actions,
+ ],
+ 'sort_fields' => [ 'logemailnum',
+ 'context',
+ 'min_level',
+ 'msgname',
+ 'to_addr',
+ '',
+ ],
+ 'links' => [ $editlink,
+ $editlink,
+ $editlink,
+ $editlink,
+ $editlink,
+ '',
+ ],
+
+ ) %>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right([ 'View system logs', 'Configuration' ]);
+
+my $add_condition_link = include('/elements/popup_link.html',
+ 'action' => $p.'edit/log_email.html?popup=1',
+ 'label' => 'Add log email condition',
+ 'actionlabel' => 'Add log email condition',
+);
+
+my $system_log_link = qq(<A HREF="${p}search/log.html">System Log</A>);
+
+my $query = {
+ 'table' => 'log_email',
+ 'select' => '*',
+ 'addl_from' => 'LEFT JOIN msg_template USING (msgnum)',
+ 'hashref' => { },
+};
+
+my $count_query = "SELECT COUNT(*) FROM log_email";
+
+my $actions = sub {
+ my $log_email = shift;
+ my $logemailnum = $log_email->logemailnum;
+ qq!<A HREF="javascript:areyousure_delete_log_email($logemailnum)">(delete)</A>!;
+};
+
+my $areyousure_onclick = include('/elements/popup_link_onclick.html',
+ 'js_action' => q(') . $p . q(misc/delete-log_email.html?logemailnum=' + logemailnum),
+ 'actionlabel' => 'Delete log email condition',
+);
+
+my $areyousure = <<EOF;
+function areyousure_delete_log_email(logemailnum) {
+ if (confirm('Are you sure you want to delete log email condition #'+logemailnum+'?')) {
+${areyousure_onclick}
+ }
+}
+EOF
+
+my $editlink = [ $p.'edit/log_email.html?logemailnum=', 'logemailnum' ];
+
+</%init>
+
diff --git a/httemplate/edit/log_email.html b/httemplate/edit/log_email.html
new file mode 100644
index 0000000..bbce7c7
--- /dev/null
+++ b/httemplate/edit/log_email.html
@@ -0,0 +1,45 @@
+<% include( 'elements/edit.html',
+ 'name_singular' => 'log email condition',
+ 'table' => 'log_email',
+ 'fields' => [
+ { 'field' => 'context',
+ 'type' => 'select',
+ 'options' => [ '', @contexts ],
+ 'labels' => { '' => '(all)', map { $_ => $_ } @contexts },
+ 'curr_value' => scalar($cgi->param('context')),
+ },
+ { 'field' => 'min_level',
+ 'type' => 'select',
+ 'options' => [ 0..7 ],
+ 'labels' => { map {$_ => $FS::Log::LEVELS[$_]} 0..7 },
+ 'curr_value' => scalar($cgi->param('min_level')),
+ },
+ 'to_addr',
+ { 'field' => 'msgnum',
+ 'type' => 'select-msg_template',
+ 'empty_label' => 'Select template',
+ 'required' => 1,
+ },
+ ],
+ 'labels' => {
+ 'context' => 'Context',
+ 'min_level' => 'Min. Level',
+ 'to_addr' => 'To',
+ 'msgnum' => 'Message',
+ },
+ 'viewall_dir' => 'browse',
+ 'popup' => $opts{'popup'},
+ 'form_init' => $opts{'popup'} ? q(<INPUT TYPE="hidden" NAME="popup" VALUE="1">) : '',
+ )
+%>
+<%once>
+my @contexts = sort FS::log_context->contexts;
+</%once>
+<%init>
+
+my %opts = @_;
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right([ 'View system logs', 'Configuration' ]);
+
+</%init>
diff --git a/httemplate/edit/process/log_email.html b/httemplate/edit/process/log_email.html
new file mode 100644
index 0000000..769e180
--- /dev/null
+++ b/httemplate/edit/process/log_email.html
@@ -0,0 +1,18 @@
+<% include('elements/process.html',
+ 'table' => 'log_email',
+ %processopts
+ ) %>
+<%init>
+
+my %opts = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right([ 'View system logs', 'Configuration' ]);
+
+my %processopts = $opts{'popup'}
+ ? ( 'popup_reload' => 'Logging email added' )
+ : ( 'redirect' => $fsurl.'browse/log_email.html?' ); # id will be needlessly appended, should be harmless
+
+</%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 9b30b6c..759ae33 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -801,6 +801,10 @@ $config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_cla
$config_misc{'Upload targets'} = [ $fsurl.'browse/upload_target.html', 'Billing and payment upload destinations' ]
if $curuser->access_right('Configuration');
+$config_misc{'System log emails'} = [ $fsurl.'browse/log_email.html', 'Configure conditions for sending email when logging' ]
+ if $curuser->access_right('View system logs')
+ || $curuser->access_right('Configuration');
+
tie my %config_menu, 'Tie::IxHash';
if ( $curuser->access_right('Configuration' ) ) {
%config_menu = (
diff --git a/httemplate/elements/tr-select-msg_template.html b/httemplate/elements/tr-select-msg_template.html
new file mode 100644
index 0000000..1f899e0
--- /dev/null
+++ b/httemplate/elements/tr-select-msg_template.html
@@ -0,0 +1,12 @@
+<% include('/elements/tr-td-label.html',
+ 'label' => $opt{'label'} || 'Message template: ',
+ 'required' => $opt{'required'} ) %>
+ <TD><% include('select-msg_template.html', %opt) %></TD>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
+
diff --git a/httemplate/misc/delete-log_email.html b/httemplate/misc/delete-log_email.html
new file mode 100644
index 0000000..cc17b15
--- /dev/null
+++ b/httemplate/misc/delete-log_email.html
@@ -0,0 +1,20 @@
+% if ($error) {
+<P STYLE="color: red"><% $error %></P>
+% } else {
+<H1>Log email condition deleted</H1>
+<SCRIPT>
+window.top.location.reload();
+</SCRIPT>
+% }
+
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right([ 'View system logs', 'Configuration' ]);
+
+my $logemailnum = $cgi->param('logemailnum');
+$logemailnum =~ /^\d+$/ or die "bad logemailnum '$logemailnum'";
+my $log_email = FS::log_email->by_key($logemailnum)
+ or die "logemailnum '$logemailnum' not found";
+my $error = $log_email->delete;
+</%init>
+
diff --git a/httemplate/search/log.html b/httemplate/search/log.html
index 9aa3736..9a61a71 100644
--- a/httemplate/search/log.html
+++ b/httemplate/search/log.html
@@ -1,6 +1,7 @@
<& elements/search.html,
'title' => 'System Log',
'name_singular' => 'event',
+ 'menubar' => \@menubar,
'html_init' => include('.head'),
'query' => $query,
'count_query' => $count_query,
@@ -210,6 +211,9 @@ my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
unless $curuser->access_right([ 'View system logs', 'Configuration' ]);
+my @menubar = ();
+push @menubar, qq(<A HREF="${fsurl}browse/log_email.html" STYLE="text-decoration: underline;">Configure conditions for sending email when logging</A>),
+
$cgi->param('min_level', 0) unless defined($cgi->param('min_level'));
$cgi->param('max_level', 7) unless defined($cgi->param('max_level'));