From: Jonathan Prykop Date: Wed, 21 Oct 2015 01:56:13 +0000 (-0500) Subject: RT#38217: Send email when logging conditions are met X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=974fceaaca8e1404750a60a4daafb568b1be5159 RT#38217: Send email when logging conditions are met --- diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index ceb347d8e..7dc54f7c4 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -6563,6 +6563,25 @@ sub tables_hashref { ], }, + '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 a4ad214d0..b07910528 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. + =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 000000000..9c53c230a --- /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, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm index d17fd41cb..1d357b1a3 100644 --- a/FS/FS/msg_template.pm +++ b/FS/FS/msg_template.pm @@ -274,7 +274,7 @@ Options are passed as a list of name/value pairs: =item cust_main -Customer object (required). +Customer object =item object @@ -324,7 +324,7 @@ sub prepare_substitutions { my( $self, %opt ) = @_; my $cust_main = $opt{'cust_main'}; # or die 'cust_main required'; - my $object = $opt{'object'} or die 'object required'; + my $object = $opt{'object'}; # or die 'object required'; warn "preparing substitutions for '".$self->msgname."'\n" if $DEBUG; diff --git a/FS/FS/msg_template/email.pm b/FS/FS/msg_template/email.pm index 377dbb17b..83ff18f19 100644 --- a/FS/FS/msg_template/email.pm +++ b/FS/FS/msg_template/email.pm @@ -164,7 +164,7 @@ Options are passed as a list of name/value pairs: =item cust_main -Customer object (required). +Customer object =item object @@ -215,7 +215,7 @@ 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 $object = $opt{'object'}; # or die 'object required'; my $hashref = $self->prepare_substitutions(%opt); @@ -365,7 +365,7 @@ sub prepare { my $env_to = join(', ', @to); my $cust_msg = FS::cust_msg->new({ - 'custnum' => $cust_main->custnum, + 'custnum' => $cust_main ? $cust_main->custnum : '', 'msgnum' => $self->msgnum, '_date' => $time, 'env_from' => $env_from, diff --git a/httemplate/browse/log_email.html b/httemplate/browse/log_email.html new file mode 100644 index 000000000..0f64dd454 --- /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' => '

' + . $add_condition_link + . ' | ' + . $system_log_link + . '

' + . '', + '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(System Log); + +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!(delete)!; +}; + +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 = < + diff --git a/httemplate/edit/log_email.html b/httemplate/edit/log_email.html new file mode 100644 index 000000000..bbce7c708 --- /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() : '', + ) +%> +<%once> +my @contexts = sort FS::log_context->contexts; + +<%init> + +my %opts = @_; + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right([ 'View system logs', 'Configuration' ]); + + diff --git a/httemplate/edit/msg_template/email.html b/httemplate/edit/msg_template/email.html index dc70ef6ec..b0c1aa31a 100644 --- a/httemplate/edit/msg_template/email.html +++ b/httemplate/edit/msg_template/email.html @@ -302,6 +302,11 @@ my %substitutions = ( '$payinfo' => 'Card/account# (masked)', '$error' => 'Decline reason', ], + 'system_log' => [ + '$logmessage' => 'Log entry message', + '$loglevel' => 'Log entry level', + '$logcontext' => 'Log entry context', + ], ); tie my %sections, 'Tie::IxHash', ( @@ -315,6 +320,7 @@ tie my %sections, 'Tie::IxHash', ( 'svc_domain'=> 'Domain service fields', 'svc_phone' => 'Phone service fields', 'svc_broadband' => 'Broadband service fields', +'system_log' => 'System log fields', ); my $widget = new HTML::Widgets::SelectLayers( diff --git a/httemplate/edit/process/log_email.html b/httemplate/edit/process/log_email.html new file mode 100644 index 000000000..769e180a8 --- /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 + + diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index ea6933198..dcc02c2b1 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -787,6 +787,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 000000000..1f899e01c --- /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'} ) %> + <% include('select-msg_template.html', %opt) %> + + +<%init> + +my %opt = @_; + + + diff --git a/httemplate/misc/delete-log_email.html b/httemplate/misc/delete-log_email.html new file mode 100644 index 000000000..cc17b15a0 --- /dev/null +++ b/httemplate/misc/delete-log_email.html @@ -0,0 +1,20 @@ +% if ($error) { +

<% $error %>

+% } else { +

Log email condition deleted

+ +% } + +<%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; + + diff --git a/httemplate/search/log.html b/httemplate/search/log.html index d1bfb6cc9..a707928d7 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, @@ -204,6 +205,9 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" unless $curuser->access_right([ 'View system logs', 'Configuration' ]); +my @menubar = (); +push @menubar, qq(Configure conditions for sending email when logging), + $cgi->param('min_level', 0) unless defined($cgi->param('min_level')); $cgi->param('max_level', 7) unless defined($cgi->param('max_level'));