use strict; use warnings; package BarnOwl::Logging; =head1 BarnOwl::Logging =head1 DESCRIPTION C implements the internals of logging. All customizations to logging should be done in the appropriate subclass of L. =head2 USAGE Modules wishing to customize how messages are logged should override the relevant subroutines in the appropriate subclass of L. Modules wishing to log errors sending outgoing messages should call L with the message that failed to be sent. =head2 EXPORTS None by default. =cut use Exporter; our @EXPORT_OK = qw(); our %EXPORT_TAGS = (all => [@EXPORT_OK]); use File::Spec; $BarnOwl::Hooks::newMessage->add("BarnOwl::Logging::log"); $BarnOwl::Hooks::startup->add("BarnOwl::Logging::_register_variables"); sub _register_variables { BarnOwl::new_variable_bool('logging', { default => 0, summary => 'turn personal logging on or off', description => "If this is set to on, personal messages are\n" . "logged in the directory specified\n" . "by the 'logpath' variable. The filename in that\n" . "directory is derived from the sender of the message." }); BarnOwl::new_variable_bool('classlogging', { default => 0, summary => 'turn class logging on or off', description => "If this is set to on, class messages are\n" . "logged in the directory specified by the\n" . "'classpath' variable. The filename in that\n" . "directory is derived from the class to which\n" . "the message was sent." }); BarnOwl::new_variable_string('logfilter', { default => '', summary => 'name of a filter controlling which messages to log', description => "If non empty, any messages matching the given filter will be logged.\n" . "This is a completely separate mechanism from the other logging\n" . "variables like logging, classlogging, loglogins, loggingdirection,\n" . "etc. If you want this variable to control all logging, make sure\n" . "all other logging variables are in their default state." }); BarnOwl::new_variable_bool('loglogins', { default => 0, summary => 'enable logging of login notifications', description => "When this is enabled, BarnOwl will log login and logout notifications\n" . "for AIM, zephyr, or other protocols. If disabled BarnOwl will not print\n" . "login or logout notifications." }); BarnOwl::new_variable_enum('loggingdirection', { default => 'both', validsettings => [qw(in out both)], summary => "specifies which kind of messages should be logged", description => "Can be one of 'both', 'in', or 'out'. If 'in' is\n" . "selected, only incoming messages are logged, if 'out'\n" . "is selected only outgoing messages are logged. If 'both'\n" . "is selected both incoming and outgoing messages are\n" . "logged.\n\n" . "Note that this variable applies to all messages. In\n" . "particular, if this variable is set to 'out', the\n" . "classlogging variable will have no effect, and no\n" . "class messages will be logged." }); BarnOwl::new_variable_string('logbasepath', { default => '~/zlog', validsettings => '', summary => 'path for logging non-zephyr messages', description => "Specifies a directory which must exist.\n" . "Each non-zephyr protocol gets its own subdirectory in\n" . "logbasepath, and messages get logged there. Note that\n" . "if the directory logbasepath/\$protocol does not exist,\n" . "logging will fail for \$protocol." }); BarnOwl::new_variable_string('logpath', { default => '~/zlog/people', validsettings => '', summary => 'path for logging personal messages', description => "Specifies a directory which must exist.\n" . "Files will be created in the directory for each sender." }); BarnOwl::new_variable_string('classlogpath', { default => '~/zlog/class', validsettings => '', summary => 'path for logging class zephyrs', description => "Specifies a directory which must exist.\n" . "Files will be created in the directory for each class." }); BarnOwl::new_variable_bool('log-to-subdirectories', { default => 0, summary => "log each protocol to its own subdirectory of logbasepath", description => "When this is enabled, BarnOwl will log each protocol to its own\n" . "subdirectory of logbasepath. When this is disabled, BarnOwl will\n" . "instead log all non-zephyr non-loopback messages to the logpath,\n" . "and prefix each filename with what would otherwise be the subdirectory\n" . "name.\n\n" . "If you enable this, be sure that the relevant directories exist;\n" . "BarnOwl will not create them for you." }); } =head2 sanitize_filename BASE_PATH FILENAME Sanitizes C and concatenates it with C. In any filename, C<"/"> and any control characters (characters which match C<[:cntrl:]> get replaced by underscores. If the resulting filename is empty or equal to C<"."> or C<"..">, it is replaced with C<"weird">. =cut sub sanitize_filename { my $base_path = BarnOwl::Internal::makepath(shift); my $filename = shift; $filename =~ s/[[:cntrl:]\/]/_/g; if ($filename eq '' || $filename eq '.' || $filename eq '..') { $filename = 'weird'; } # The original C code also removed characters less than '!' and # greater than or equal to '~', marked file names beginning with a # non-alphanumeric or non-ASCII character as 'weird', and rejected # filenames longer than 35 characters. return File::Spec->catfile($base_path, $filename); } =head2 get_filenames MESSAGE Returns a list of filenames in which to log the passed message. This method calls C on C to determine the list of filenames to which C gets logged. All filenames are relative to Clog_path>. If Clog_to_all_file> returns true, then the filename C<"all"> is appended to the list of filenames. Filenames are sanitized by C. =cut sub get_filenames { my ($m) = @_; my @filenames = $m->log_filenames; push @filenames, 'all' if $m->log_to_all_file; return map { sanitize_filename($m->log_path, $_) } @filenames; } =head2 should_log_message MESSAGE Determines whether or not the passed message should be logged. To customize the behavior of this method, override L. =cut sub should_log_message { my ($m) = @_; # If there's a logfilter and this message matches it, log. # pass quiet=1, because we don't care if the filter doesn't exist return 1 if BarnOwl::message_matches_filter($m, BarnOwl::getvar('logfilter'), 1); # otherwise we do things based on the logging variables # skip login/logout messages if appropriate return 0 if $m->is_loginout && BarnOwl::getvar('loglogins') eq 'off'; # check direction return 0 if $m->is_outgoing && BarnOwl::getvar('loggingdirection') eq 'in'; return 0 if $m->is_incoming && BarnOwl::getvar('loggingdirection') eq 'out'; return $m->should_log; } =head2 log MESSAGE Call this method to (potentially) log a message. To customize the behavior of this method for your messages, override L, L, L, and/or L. =cut sub log { my ($m) = @_; return unless defined $m; return unless BarnOwl::Logging::should_log_message($m); my $log_text = $m->log; foreach my $filename (BarnOwl::Logging::get_filenames($m)) { BarnOwl::Logging::enqueue_text($log_text, $filename); } } =head2 log_outgoing_error MESSAGE Call this method to (potentially) log an error in sending an outgoing message. Errors get logged to the same file(s) as successful messages. To customize the behavior of this method for your messages, override L, L, L, and/or L. =cut sub log_outgoing_error { my ($m) = @_; return unless BarnOwl::Logging::should_log_message($m); my $log_text = $m->log_outgoing_error; foreach my $filename (BarnOwl::Logging::get_filenames($m)) { BarnOwl::Logging::enqueue_text($log_text, $filename); } } 1;