4 use base qw( FS::Record );
5 use FS::Record qw( qsearch qsearchs dbdef );
6 use FS::UID qw( dbh driver_name );
11 FS::log - Object methods for log records
17 $record = new FS::log \%hash;
18 $record = new FS::log { 'column' => 'value' };
20 $error = $record->insert;
22 $error = $new_record->replace($old_record);
24 $error = $record->delete;
26 $error = $record->check;
30 An FS::log object represents a log entry. FS::log inherits from
31 FS::Record. The following fields are currently supported:
35 =item lognum - primary key
37 =item _date - Unix timestamp
39 =item agentnum - L<FS::agent> to which the log pertains. If it involves a
40 specific customer, package, service, invoice, or other agent-specific object,
41 this will be set to that agentnum.
43 =item tablename - table name to which the log pertains, if any.
45 =item tablenum - foreign key to that table.
47 =item level - log level: 'debug', 'info', 'notice', 'warning', 'error',
48 'critical', 'alert', 'emergency'.
50 =item message - contents of the log entry
60 Creates a new log entry. Use FS::Log instead of calling this directly,
67 =item insert [ CONTEXT... ]
69 Adds this record to the database. If there is an error, returns the error,
70 otherwise returns false.
72 CONTEXT may be a list of context tags to attach to this record.
77 # not using process_o2m for this, because we don't have a web interface
79 my $error = $self->SUPER::insert;
80 return $error if $error;
82 my $context = FS::log_context->new({
83 'lognum' => $self->lognum,
86 $error = $context->insert;
87 return $error if $error;
92 # the insert method can be inherited from FS::Record
94 sub delete { die "Log entries can't be modified." };
96 sub replace { die "Log entries can't be modified." };
100 Checks all fields to make sure this is a valid example. If there is
101 an error, returns the error, otherwise returns false. Called by the insert
110 $self->ut_numbern('lognum')
111 || $self->ut_number('_date')
112 || $self->ut_numbern('agentnum')
113 || $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
114 || $self->ut_textn('tablename')
115 || $self->ut_numbern('tablenum')
116 || $self->ut_number('level')
117 || $self->ut_text('message')
119 return $error if $error;
121 if ( my $tablename = $self->tablename ) {
122 my $dbdef_table = dbdef->table($tablename)
123 or return "tablename '$tablename' does not exist";
124 $error = $self->ut_foreign_key('tablenum',
126 $dbdef_table->primary_key);
127 return $error if $error;
135 Returns the context for this log entry, as an array, from least to most
142 map { $_->context } qsearch({
143 table => 'log_context',
144 hashref => { lognum => $self->lognum },
145 order_by => 'ORDER BY logcontextnum ASC',
157 Returns a qsearch hash expression to search for parameters specified in
158 HASHREF. Valid parameters are:
164 =item date - arrayref of start and end date
166 =item level - either a specific level, or an arrayref of min and max level
168 =item context - a context string that the log entry must have. This may
169 change in the future to allow searching for combinations of context strings.
171 =item object - any database object, to find log entries related to it.
173 =item tablename, tablenum - alternate way of specifying 'object'.
175 =item custnum - a customer number, to find log entries related to the customer
176 or any of their subordinate objects (invoices, packages, etc.).
178 =item message - a text string to search in messages. The search will be
179 a case-insensitive LIKE with % appended at both ends.
185 # used for custnum search: all tables with custnums
188 sub _setup_table_stubs {
214 my $pkey = dbdef->table($table)->primary_key;
216 "log.tablename = '$table' AND ".
217 "EXISTS(SELECT 1 FROM $table WHERE log.tablenum = $table.$pkey AND ".
218 "$table.custnum = "; # needs a closing )
222 "(log.tablename LIKE 'svc_%' OR log.tablename = 'cust_svc') AND ".
223 "EXISTS(SELECT 1 FROM cust_svc JOIN cust_pkg USING (svcnum) WHERE ".
224 "cust_pkg.custnum = "; # needs a closing )
228 my ($class, $params) = @_;
235 if ( $params->{'agentnum'} =~ /^(\d+)$/ ) {
244 if ( $params->{'custnum'} =~ /^(\d+)$/ ) {
245 _setup_table_stubs() unless @table_stubs;
247 my @orwhere = map { "( $_ $custnum) )" } @table_stubs;
248 push @where, join(' OR ', @orwhere);
255 if ( ref $params->{'level'} eq 'ARRAY' ) {
256 my ($min, $max) = @{ $params->{'level'} };
257 if ( $min =~ /^\d+$/ ) {
258 push @where, "log.level >= $min";
260 if ( $max =~ /^\d+$/ ) {
261 push @where, "log.level <= $max";
263 } elsif ( $params->{'level'} =~ /^(\d+)$/ ) {
264 push @where, "log.level = $1";
271 if ( ref $params->{'date'} eq 'ARRAY' ) {
272 my ($beg, $end) = @{ $params->{'date'} };
273 if ( $beg =~ /^\d+$/ ) {
274 push @where, "log._date >= $beg";
276 if ( $end =~ /^\d+$/ ) {
277 push @where, "log._date <= $end";
285 if ( $params->{'object'} and $params->{'object'}->isa('FS::Record') ) {
286 my $table = $params->{'object'}->table;
287 my $pkey = dbdef->table($table)->primary_key;
288 my $tablenum = $params->{'object'}->get($pkey);
289 if ( $table and $tablenum ) {
290 push @where, "log.tablename = '$table'", "log.tablenum = $tablenum";
292 } elsif ( $params->{'tablename'} =~ /^(\w+)$/ ) {
294 if ( $params->{'tablenum'} =~ /^(\d+)$/ ) {
295 push @where, "log.tablename = '$table'", "log.tablenum = $1";
303 if ( $params->{'message'} ) { # can be anything, really, so escape it
304 my $quoted_message = dbh->quote('%' . $params->{'message'} . '%');
305 my $op = (driver_name eq 'Pg' ? 'ILIKE' : 'LIKE');
306 push @where, "log.message $op $quoted_message";
313 if ( $params->{'context'} ) {
314 my $quoted = dbh->quote($params->{'context'});
316 "EXISTS(SELECT 1 FROM log_context WHERE log.lognum = log_context.lognum ".
317 "AND log_context.context = $quoted)";
320 # agent virtualization
321 my $access_user = $FS::CurrentUser::CurrentUser;
322 push @where, $access_user->agentnums_sql(
324 viewall_right => 'Configuration',
330 $extra_sql .= 'WHERE ' . join(' AND ', @where) if @where;
331 my $count_query = 'SELECT COUNT(*) FROM log '.$extra_sql;
336 'extra_sql' => $extra_sql,
337 'count_query' => $count_query,
338 'order_by' => 'ORDER BY _date ASC',
339 #addl_from, not needed
349 L<FS::Record>, schema.html from the base documentation.