4 use base qw( FS::Record );
5 use FS::Record qw( qsearch qsearchs dbdef );
6 use FS::UID qw( dbh driver_name );
12 FS::log - Object methods for log records
18 $record = new FS::log \%hash;
19 $record = new FS::log { 'column' => 'value' };
21 $error = $record->insert;
23 $error = $new_record->replace($old_record);
25 $error = $record->delete;
27 $error = $record->check;
31 An FS::log object represents a log entry. FS::log inherits from
32 FS::Record. The following fields are currently supported:
36 =item lognum - primary key
38 =item _date - Unix timestamp
40 =item agentnum - L<FS::agent> to which the log pertains. If it involves a
41 specific customer, package, service, invoice, or other agent-specific object,
42 this will be set to that agentnum.
44 =item tablename - table name to which the log pertains, if any.
46 =item tablenum - foreign key to that table.
48 =item level - log level: 'debug', 'info', 'notice', 'warning', 'error',
49 'critical', 'alert', 'emergency'.
51 =item message - contents of the log entry
61 Creates a new log entry. Use FS::Log instead of calling this directly,
68 =item insert [ CONTEXT... ]
70 Adds this record to the database. If there is an error, returns the error,
71 otherwise returns false.
73 CONTEXT may be a list of context tags to attach to this record.
75 Will send emails according to the conditions in L<FS::log_email>.
80 # not using process_o2m for this, because we don't have a web interface
82 my $error = $self->SUPER::insert;
83 return $error if $error;
84 my $contexts = {}; #for quick checks when sending emails
86 my $context = FS::log_context->new({
87 'lognum' => $self->lognum,
90 $error = $context->insert;
91 return $error if $error;
94 foreach my $log_email (
100 'value' => $self->level,
105 # shouldn't be a lot of these, so not packing this into the qsearch
106 next if $log_email->context && !$contexts->{$log_email->context};
107 my $msg_template = qsearchs('msg_template',{ 'msgnum' => $log_email->msgnum });
108 unless ($msg_template) {
109 warn "Could not send email when logging, could not load message template for logemailnum " . $log_email->logemailnum;
112 my $emailerror = $msg_template->send(
113 'msgtype' => 'admin',
114 'to' => $log_email->to_addr,
116 'loglevel' => $FS::Log::LEVELS[$self->level], # which has hopefully been loaded...
117 'logcontext' => $log_email->context, # use the one that triggered the email
118 'logmessage' => $self->message,
121 warn "Could not send email when logging: $emailerror" if $emailerror;
126 # these methods can be inherited from FS::Record
128 sub delete { die "Log entries can't be modified." };
130 sub replace { die "Log entries can't be modified." };
134 Checks all fields to make sure this is a valid example. If there is
135 an error, returns the error, otherwise returns false. Called by the insert
144 $self->ut_numbern('lognum')
145 || $self->ut_number('_date')
146 || $self->ut_numbern('agentnum')
147 || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
148 || $self->ut_textn('tablename')
149 || $self->ut_numbern('tablenum')
150 || $self->ut_number('level')
151 || $self->ut_anything('message')
153 return $error if $error;
155 if ( my $tablename = $self->tablename ) {
156 my $dbdef_table = dbdef->table($tablename)
157 or return "tablename '$tablename' does not exist";
158 $error = $self->ut_foreign_key('tablenum',
160 $dbdef_table->primary_key);
161 return $error if $error;
169 Returns the context for this log entry, as an array, from least to most
176 map { $_->context } qsearch({
177 table => 'log_context',
178 hashref => { lognum => $self->lognum },
179 order_by => 'ORDER BY logcontextnum ASC',
191 Returns a qsearch hash expression to search for parameters specified in
192 HASHREF. Valid parameters are:
198 =item date - arrayref of start and end date
200 =item level - either a specific level, or an arrayref of min and max level
202 =item context - a context string that the log entry must have. This may
203 change in the future to allow searching for combinations of context strings.
205 =item object - any database object, to find log entries related to it.
207 =item tablename, tablenum - alternate way of specifying 'object'.
209 =item custnum - a customer number, to find log entries related to the customer
210 or any of their subordinate objects (invoices, packages, etc.).
212 =item message - a text string to search in messages. The search will be
213 a case-insensitive LIKE with % appended at both ends.
219 # used for custnum search: all tables with custnums
222 sub _setup_table_stubs {
248 my $pkey = dbdef->table($table)->primary_key;
250 "log.tablename = '$table' AND ".
251 "EXISTS(SELECT 1 FROM $table WHERE log.tablenum = $table.$pkey AND ".
252 "$table.custnum = "; # needs a closing )
256 "(log.tablename LIKE 'svc_%' OR log.tablename = 'cust_svc') AND ".
257 "EXISTS(SELECT 1 FROM cust_svc JOIN cust_pkg USING (svcnum) WHERE ".
258 "cust_pkg.custnum = "; # needs a closing )
262 my ($class, $params) = @_;
269 if ( $params->{'agentnum'} =~ /^(\d+)$/ ) {
278 if ( $params->{'custnum'} =~ /^(\d+)$/ ) {
279 _setup_table_stubs() unless @table_stubs;
281 my @orwhere = map { "( $_ $custnum) )" } @table_stubs;
282 push @where, join(' OR ', @orwhere);
289 if ( ref $params->{'level'} eq 'ARRAY' ) {
290 my ($min, $max) = @{ $params->{'level'} };
291 if ( $min =~ /^\d+$/ ) {
292 push @where, "log.level >= $min";
294 if ( $max =~ /^\d+$/ ) {
295 push @where, "log.level <= $max";
297 } elsif ( $params->{'level'} =~ /^(\d+)$/ ) {
298 push @where, "log.level = $1";
305 if ( ref $params->{'date'} eq 'ARRAY' ) {
306 my ($beg, $end) = @{ $params->{'date'} };
307 if ( $beg =~ /^\d+$/ ) {
308 push @where, "log._date >= $beg";
310 if ( $end =~ /^\d+$/ ) {
311 push @where, "log._date <= $end";
319 if ( $params->{'object'} and $params->{'object'}->isa('FS::Record') ) {
320 my $table = $params->{'object'}->table;
321 my $pkey = dbdef->table($table)->primary_key;
322 my $tablenum = $params->{'object'}->get($pkey);
323 if ( $table and $tablenum ) {
324 push @where, "log.tablename = '$table'", "log.tablenum = $tablenum";
326 } elsif ( $params->{'tablename'} =~ /^(\w+)$/ ) {
328 if ( $params->{'tablenum'} =~ /^(\d+)$/ ) {
329 push @where, "log.tablename = '$table'", "log.tablenum = $1";
337 if ( $params->{'message'} ) { # can be anything, really, so escape it
338 my $quoted_message = dbh->quote('%' . $params->{'message'} . '%');
339 my $op = (driver_name eq 'Pg' ? 'ILIKE' : 'LIKE');
340 push @where, "log.message $op $quoted_message";
347 if ( $params->{'context'} ) {
348 my $quoted = dbh->quote($params->{'context'});
350 "EXISTS(SELECT 1 FROM log_context WHERE log.lognum = log_context.lognum ".
351 "AND log_context.context = $quoted)";
354 # agent virtualization
355 my $access_user = $FS::CurrentUser::CurrentUser;
356 push @where, $access_user->agentnums_sql(
358 viewall_right => 'Configuration',
364 $extra_sql .= 'WHERE ' . join(' AND ', @where) if @where;
365 my $count_query = 'SELECT COUNT(*) FROM log '.$extra_sql;
370 'extra_sql' => $extra_sql,
371 'count_query' => $count_query,
372 'order_by' => 'ORDER BY _date ASC',
373 #addl_from, not needed
383 L<FS::Record>, schema.html from the base documentation.