# # Copyright (c) 2004-2007 - Consultas, PKG.fr # # This file is part of A2P. # # A2P is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # A2P is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with A2P; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # # $Id: Syslog.pm 3 2007-10-18 16:20:19Z guillaume $ # package A2P::Syslog; use strict ; use Socket ; use Errno qw( EINTR ); use Time::HiRes qw( usleep gettimeofday tv_interval ); use IO::Socket ; use MIME::Base64 ; use A2P::Globals ; use A2P::Com qw( GetCom comREQ comCOM comLOG TakeComLocking ReleaseComLocking ); BEGIN { use Exporter (); our ( $VERSION , @ISA , @EXPORT , @EXPORT_OK , @TT ); $VERSION = sprintf "%s", q$Rev: 762 $ =~ /(\d[0-9.]+)\s+/ ; @ISA = qw(Exporter); @EXPORT = qw( &Debug &Info &Notice &Error &Warn &Alert $loggertid &DirectLog &SetLogger &getBackLogs &AlertError &debugdev &NotImplemented &ResetDebug ); @EXPORT_OK= qw( &AttachZipToAlertError ); # Set timer ref to starting time @TT = ( $^T , ( $^T * 1_000_000 ) % 1_000_000 ) unless @TT ; } our $VERSION ; our $loggertid = 0 ; my $Logger = 0 ; my $loggingname = $Progname ; # Shared timer to get syslog timestamps synchronized our @TT ; # Log backup my @BackLogs = () ; # Locking var to not get coflicts with coms my $lockfile = undef ; my $locked = 0 ; # Syslog socket sub facility { 6 << 3 } # LOG_LPR Facility my $who = "" ; sub OpenSyslog { my $toconnect = sockaddr_un($SYSLOG_PATH); unless ( $toconnect ) { warn "Can't locate $SYSLOG_PATH: $!\n" ; return "" ; } unless ( socket( $SYSLOG, AF_UNIX, SOCK_STREAM, 0 )) { warn "Can't open Syslog unix stream socket: $!\n" ; return "" ; } if (!connect($SYSLOG,$toconnect)) { unless (socket( $SYSLOG, AF_UNIX, SOCK_DGRAM, 0 )) { warn "Can't open Syslog unix datagram socket: $!\n" ; return "" ; } unless (connect($SYSLOG,$toconnect)) { warn "Can't connect to Syslog unix datagram socket: $!\n" ; return "" ; } } return $loggingname . ( $LOCKID ? "-$LOCKID" : "" ) . "[$$]" ; } my %Level = ( 1 => 'alert', 3 => 'error' , 4 => 'warning' , 5 => 'notice' , 6 => 'info' , 7 => 'debug' ); sub DirectLog { # First argument should be log level # Second is an array of messages to log # Third should be defined to get message up to parent # and content debugging info when required return unless ( ref($_[1]) =~/^ARRAY/ ); my $level = shift ; my $ref = shift ; $level = 6 unless ( defined($level) and $level ); my @hdr = $NO_SYSLOG_DEBUG ? () : ( ×tamp(), '>' ); push @hdr, "$Level{$level}: $loggingname:", @_ if @_ ; # Reset error status for debugging output problems $! = 0 ; if ( $loggertid and $loggertid != $$ and $Logger and ! $ADVANCED_DEBUGGING ) { close($SYSLOG) if (defined($SYSLOG)); # Keep who is logging when logging in file, and not at debug level push @hdr, $Level{$level} . ":", $loggingname . "[$$]:" if ( $LOGFILE_VS_SYSLOG and $level < 7 ); foreach my $logline ( @{$ref} ) { my $comlog = &GetCom( comLOG , $level , &KeepValidatedLog( \@hdr, $logline )); unless($Logger->DoLog($comlog)) { &debugdev("Log not sent on $Logger: $logline"); # Try to log directly by recall ourselves but avoiding this case my $keep = $ADVANCED_DEBUGGING ; $ADVANCED_DEBUGGING = 1 ; &DirectLog( $level , $ref, @_ ); $ADVANCED_DEBUGGING = $keep ; } } } elsif ( $LOGFILE_VS_SYSLOG or ( $level == 7 and $DEBUG_IN_FILE )) { my $LOG ; unshift @hdr, scalar(localtime), "[$$]:" ; open( $LOG, '>>', $LOGFILENAME ) or warn "Can't open '$LOGFILENAME' for appending: $!\n", return 0 ; &TakeComLocking($LOG); my $test = 0 ; map { $test += print $LOG &KeepValidatedLog( \@hdr, $_ ), "\n" } @{$ref} ; $test == @{$ref} ? &UPSTAT('GOOD-LOGPRINT') : &UPSTAT('BAD-LOGPRINT'); &ReleaseComLocking($LOG); close($LOG); } else { $who = &OpenSyslog(); if ($who) { $level += facility ; unshift @hdr, "<$level>$who:" ; my $test = 0 ; my $tempo = 0 ; foreach my $line ( @{$ref} ) { my $log = &KeepValidatedLog( \@hdr, $line ); my $sent = $! = 0 ; while ( ! defined($sent = send( $SYSLOG , $log . "\0" , 0 ))) { # Retry only when we got a interrupted system call unless ( $! == EINTR ) { &debugdev("With line '$line'...", "Can't send to syslog '$log'"); &debugdev("And errno[".int($!)."]= $!") if $! ; last ; } usleep $USLEEP ; $! = 0 ; } $test ++ ; $tempo += $sent ; } $test == @{$ref} ? &UPSTAT('GOOD-SYSLOGPRINT') : &UPSTAT('BAD-SYSLOGPRINT'); close($SYSLOG); } } $! = 0 ; } sub KeepValidatedLog { my $timeref = shift ; my $logline = shift ; $logline =~ s/[^ -~]/ /go ; # Keep ref to log if requested if ( $MAX_BACKLOG and ! $KEEP_DYNAMIC_JOB_LOG ) { shift @BackLogs until ( @BackLogs < $MAX_BACKLOG ); push @BackLogs, [ @{$timeref} , $logline ] ; } local $" = ' ' ; return ( @{$timeref} ? "@{$timeref} " : "" ) . $logline ; } sub getBackLogs { # Re-assemble logs ref return map { join('', @{$_}) } @BackLogs ; } sub timestamp { return "" if ( $NO_SYSLOG_DEBUG and ! $MAX_BACKLOG and ! $STDOUT_DEBUG); # Keep timer in the current minute my @tt = &gettimeofday() ; return sprintf("%.8d", (( $tt[0] - $TT[0] ) * 1_000_000 + ( $tt[1] - $TT[1] )) % 60_000_000 ); } ############################################################################# ## Standard system logging functions ## ############################################################################# my $LastCount = 0 ; my $LastDebugMessage = "" ; sub SetLogger { $Logger = shift ; $loggingname = $0 ; } sub debugdev { my $time = localtime(time); print STDERR map { $time, ×tamp, ": $Progname\[$$]: [DEV]: ", $_, "\n" } @_ ; } sub NotImplemented { my ( $package, $filename, $line, $sub ) = caller ; &DirectLog( 5 , [ "Need implementation".(@_?" of '@_'":"")." in $filename" ], $package.", ".$sub."(), L.".$line.":"); } my %DebugMods = () ; my $ReversedDebugModList = 0 ; sub ResetDebug { my $last = $ReversedDebugModList ; $ReversedDebugModList = $NODEBUG_MOD_LIST =~ /^(!)/ ; $NODEBUG_MOD_LIST =~ s/[!]//g ; %DebugMods = map { $_ => 1 } split(/[,;\s]+/,$NODEBUG_MOD_LIST) ; return if ($NO_SYSLOG_DEBUG); &DirectLog( 7 , [ "Switching NODEBUG_MOD_LIST comportement to " . ($ReversedDebugModList?"":"not ") . "debug on ".join(" ",keys(%DebugMods)) ] ) unless ( $ReversedDebugModList == $last ); } my $lastdebug = [ &gettimeofday() ] ; sub Debug { return &debugdev(@_) if ( $STDOUT_DEBUG ); return if ( $NO_SYSLOG_DEBUG and ( $KEEP_DYNAMIC_JOB_LOG or ! $MAX_BACKLOG )); my ($package, $filename, $line) = caller ; return if ($ReversedDebugModList ^ defined($DebugMods{$package})); my @sub = caller(1); @sub = caller(2) if ( defined($sub[3]) and $sub[3] =~ /Debug$/ ); $sub[3] = "(none)" unless (@sub > 3); my ( $sub ) = $sub[3] =~ /^.*::([^:]*)$/ ; return if ( $sub and $NODEBUG_SUB_LIST =~ /$sub/ ); if ( $#_ or $_[0] ne $LastDebugMessage or &tv_interval($lastdebug) > 1 ) { if ($NO_SYSLOG_DEBUG) { &KeepValidatedLog( [ ×tamp() ], "Last message repeated $LastCount times") if $LastCount ; map { &KeepValidatedLog( [ ×tamp(), "$package,". ( $sub ? "$sub," : ""), "L.$line:" ], $_ ) } @_ ; } else { &DirectLog( 7 , [ " last message repeated $LastCount times" ] , "..." ) if $LastCount ; &DirectLog( 7 , \@_, "$package,".( $sub ? "$sub," : ""), "L.$line:" ); } $LastDebugMessage = defined($_[$#_]) ? $_[$#_] : "" ; $LastCount = 0 if $LastCount ; $lastdebug = [ &gettimeofday() ] ; } else { $LastCount ++ ; } } sub Info { &DirectLog( 6 , \@_ ); } sub Notice { &DirectLog( 5 , \@_ ); } sub Error { if ($NO_SYSLOG_DEBUG and ! $ADVANCED_DEBUGGING) { &DirectLog( 3 , \@_ ); } else { my ( $package, $filename, $line, $sub ) = caller ; $filename =~ m|([^/]+)$| ; &DirectLog( 3 , \@_ , "$package, L.$line:" ); &DirectLog( 7 , \@_ , "ERROR: $package, L.$line:" ); ($package, $filename, $line, $sub) = caller(1); return 0 unless (defined($filename) and defined($line) and defined($sub) and $sub); $filename =~ m|([^/]+)$| ; &DirectLog( 7 , \@_ , "ERROR: $1, $sub, L.$line:" ); ($package, $filename, $line, $sub) = caller(2); return 0 unless (defined($filename) and defined($line) and defined($sub) and $sub); $filename =~ m|([^/]+)$| ; &DirectLog( 7 , \@_ , "ERROR: $1, $sub, L.$line:" ); } 0 ; } sub Warn { &DirectLog( 4 , \@_ ); } sub Alert { return &Warn("Alert called without message") unless ( @_ and $_[0] ); if ( -x $MAIL_BIN ) { if ( $ALERT_MAILS ) { open(*MAIL,"|$MAIL_BIN $ALERT_MAILS") or &Warn("Can't open mailer program: $!"); print MAIL "Subject: [$Progname;$LOCKID] $_[0]\n"; close(MAIL); &Info("Alert sent to '$ALERT_MAILS'"); } &AlertError(@_) if $ERROR_MAILS ; } else { &Warn("Can't open mailer program as it is not launchable"); } # Reduce alert to syslog to only give the alert title &DirectLog( 1 , [ $_[0] ] ); } my $zipfile = "" ; sub AttachZipToAlertError { $zipfile = shift ; } sub AlertError { return &Warn("Alert called without message") unless ( @_ and $_[0] ); if ( -x $MAIL_BIN ) { if ( $ERROR_MAILS ) { open(*MAIL,"|$MAIL_BIN $ERROR_MAILS") or return &Warn("Can't open mailer program: $!"); my $boundary = "_----------=_" . int(time) . $$ ; print MAIL "Subject: [$Progname;$LOCKID] $_[0]\n" ; print MAIL "Content-Transfer-Encoding: binary Content-Type: multipart/mixed; boundary=$boundary MIME-Version: 1.0 X-Mailer: $loggingname v$VERSION (", __PACKAGE__, " rev $VERSION) This is a multi-part message in MIME format. --$boundary " if ( $zipfile and -e $zipfile ); # Anyway insert alert content map { print MAIL $_ , "\n" } @_ ; if ( $zipfile and -e $zipfile ) { my $buf ; my ( $zipname ) = $zipfile =~ m|^.*/([^/]+)$| ; if ( open(*ZIP, '<', $zipfile)) { binmode(ZIP); print MAIL " --$boundary Content-Transfer-Encoding: base64 Content-Type: application/zip; name='$zipname'\n\n" ; while (read(ZIP,$buf,60*57)) { print MAIL encode_base64($buf); } } $zipfile = "" ; print MAIL "\n\n--$boundary--\n\n" ; } close(MAIL); &Info("Alert with errors sent to '$ERROR_MAILS'"); &Debug("Mail sent"); } } else { &Warn("Can't open mailer program as it is not launchable"); } } &Debug("Module " . __PACKAGE__ . " v$VERSION loaded") unless ( $0 =~ /cgi/ ); 1;