You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by ms...@apache.org on 2004/02/11 20:42:47 UTC

svn commit: rev 6624 - incubator/spamassassin/trunk/spamd

Author: mss
Date: Wed Feb 11 11:42:46 2004
New Revision: 6624

Modified:
   incubator/spamassassin/trunk/spamd/spamd.raw
Log:
bug 1360: Add support for logging to a file instead of syslog.

There might be a bug in this code, I'm not sure how the logfile should be shared; see line 618.


Modified: incubator/spamassassin/trunk/spamd/spamd.raw
==============================================================================
--- incubator/spamassassin/trunk/spamd/spamd.raw	(original)
+++ incubator/spamassassin/trunk/spamd/spamd.raw	Wed Feb 11 11:42:46 2004
@@ -233,14 +233,161 @@
   }
 }
 
-# This can be changed on the command line with the -s flag; special cases:
+
+### Begin initialization of logging ########################
+
+# The syslog facility can be changed on the command line with the
+# --syslog flag. Special cases are:
 # * A log facility of 'stderr' will log to STDERR
 # * " "   "        "  'null' disables all logging
-my $log_facility = $opt{'syslog'}        || 'mail';
-my $log_socket   = $opt{'syslog-socket'} || 'unix';
-$log_facility = 'stderr' if $log_socket eq   'none';
-$log_facility = 'null'   if $log_facility eq 'stderr' # don't duplicate log
-                            and $opt{'debug'};        # messages in debug mode
+# * " "   "        "  'file' logs to the file "spamd.log"
+# * Any facility containing non-word characters is interpreted as the name
+#   of a specific logfile
+my $log_facility  =    $opt{'syslog'}         || 'mail';
+# The socket to log over can be changed on the command line with the
+# --syslog-socket flag. Logging to any file handler (either a specific log
+# file or STDERR) is internally represented by a socket 'file', no logging
+# at all is 'none'. The latter is different from --syslog-socket=none which
+# gets mapped to --syslog=stderr and such --syslog-socket=file. An internal
+# socket of 'none' means as much as --syslog=null. Sounds complicated? It is.
+# But it works.
+my $log_socket    = lc($opt{'syslog-socket'}) || 'unix';
+# This is the default log file; it can be changed on the command line
+# via a --syslog flag containing non-word characters.
+my $log_file      = "spamd.log";
+
+# A specific log file was given (--syslog=/path/to/file).
+if ($log_facility =~ /\W/) {
+  $log_file       = $log_facility;
+  $log_socket     = 'file';
+}
+# The generic log file was requested (--syslog=file).
+elsif (lc($log_facility) eq 'file') {
+  $log_socket     = 'file';
+}
+# The casing is kept only if the facility specified a file.
+else {
+  $log_facility = lc($log_facility);
+}
+# Either above or at the command line the socket was set
+# to 'file' (--syslog-socket=file).
+if ($log_socket eq 'file') {
+  $log_facility   = 'file';
+}
+# The socket 'none' (--syslog-socket=none) historically
+# represents logging to STDERR.
+elsif ($log_socket eq 'none') {
+  $log_facility   = 'stderr';
+}
+# Either above or at the command line the facility was set
+# to 'stderr' (--syslog=stderr).
+if ($log_facility eq 'stderr') {
+  $log_socket     = 'file';
+}
+
+# Logging via syslog is requested. Falling back to INET and then STDERR
+# if opening a UNIX socket fails.
+if ($log_socket ne 'file' and $log_facility ne 'null') {
+  warn "trying to connect to syslog/${log_socket}...\n" if $opt{'debug'};
+  eval {
+    defined(setlogsock($log_socket)) || die $!;
+    # The next call is required to actually open the socket.
+    # FIXME: mss: wouldn't a openlog('spamd', 'cons,pid,ndelay', $log_facility); be better?
+    syslog('debug', "%s", "spamd starting");
+  };
+  my $err = $@;
+  chomp($err);
+
+  # Solaris sometimes doesn't support UNIX-domain syslog sockets apparently;
+  # same is true for perl 5.6.0 build on an early version of Red Hat 7!
+  # In that case we try it with INET.
+  if ($err and $log_socket ne 'inet') {
+    warn "connection failed: $err\n" .
+         "trying to connect to syslog/inet...\n"  if $opt{'debug'};
+    eval {
+      defined(setlogsock('inet')) || die $!;
+      syslog('debug', "%s", "spamd starting");
+      syslog('debug', "%s", "failed to setlogsock(${log_socket}): $err");
+      syslog('debug', "%s", "falling back to inet (you might want to use --syslog-socket=inet)");
+    };
+    $log_socket   = 'inet' unless $@;
+  }
+
+  # fall back to stderr if all else fails
+  if ($@) {
+    warn "failed to setlogsock(${log_socket}): $err\n" .
+         "reporting logs to stderr\n";
+    $log_facility = 'stderr';
+  } else {
+    warn "no error connecting to syslog/${log_socket}\n" if $opt{'debug'};
+  }
+}
+# The user wants to log to some file -- open it on STDLOG. Falling back to STDERR
+# if opening the file fails.
+elsif ($log_facility eq 'file') {
+  unless(open(STDLOG, ">> $log_file")) {
+    warn "failed to open logfile ${log_file}: $!\n" .
+         "reporting logs to stderr\n";
+    $log_facility = 'stderr';
+  }
+}
+
+# Either one of the above failed ot logging to STDERR is explicitly requested --
+# make STDLOG a dup so we don't have to handle so many special cases later on.
+if ($log_facility eq 'stderr') {
+  open(STDLOG, ">&STDERR") || die "Can't duplicate stderr: $!\n";
+  $log_socket     = 'file';
+}
+
+warn "logging enabled:\n" .
+        "\tfacility: ${log_facility}\n" .
+        "\tsocket:   ${log_socket}\n" .
+        "\toutput:   " . (
+          $log_facility eq 'file'   ? ${log_file} :
+          $log_facility eq 'stderr' ? 'stderr'    :
+          $log_facility eq 'null'   ? 'debug'     :
+                                      'syslog'
+        ) . "\n" if $opt{'debug'};
+
+# Don't duplicate log messages in debug mode.
+if ($log_facility eq 'stderr' and $opt{'debug'}) {
+  warn "logging to stderr disabled: already debugging to stderr\n";
+  $log_facility   = 'null';
+}
+# Either above or at the command line all logging was disabled (--syslog=null).
+if ($log_facility eq 'null') {
+  $log_socket     = 'none';
+}
+
+# Close the logfile on exit.
+END {
+  close(STDLOG) if $log_socket eq 'file';
+}
+
+# The code above was quite complicated. Make sure everything fits together.
+# These combinations are allowed:
+#   * socket = file
+#      ^ ^-> facility = stderr
+#      '---> facility = file
+#   * socket = none
+#      ^-> facility = null
+#   * socket = (unix|inet|...)
+#      --> facility = (mail|daemon|...)
+die "fatal: internal error while setting up logging: values don't match:\n" .
+    "\targuments:\n" .
+      "\t\t--syslog=$opt{'syslog'} --syslog-socket=$opt{'syslog-socket'}\n" .
+    "\tvalues:\n" .
+      "\t\tfacility: ${log_facility}\n" .
+      "\t\tsocket:   ${log_socket}\n" .
+      "\t\tfile:     ${log_file}\n" .
+    "\tplease report to http://bugzilla.spamassassin.org -- thank you\n"
+  if ($log_socket eq 'file' and ($log_facility ne 'stderr' and $log_facility ne 'file'))
+  or (($log_facility eq 'stderr' or $log_facility eq 'file') and $log_socket ne 'file')
+  or ($log_socket eq 'none' and $log_facility ne 'null')
+  or ($log_facility eq 'null' and $log_socket ne 'none');
+
+### End initialization of logging ##########################
+
 
 # REIMPLEMENT: if $log_socket is none, fall back to log_facility 'stderr'.
 # If log_fac is stderr and $opt{'debug'}, set log_fac to 'null' to avoid
@@ -294,28 +441,6 @@
 
 # Do whitelist later in tmp dir. Side effect: this will be done as -u user.
 
-if ($log_facility ne 'stderr') {
-  eval {
-    setlogsock($log_socket);
-    syslog('debug', "spamd starting");  # required to actually open the socket
-  };
-
-  # Solaris sometimes doesn't support UNIX-domain syslog sockets apparently;
-  # same is true for perl 5.6.0 build on an early version of Red Hat 7!
-  if ($@) {
-    eval {
-      setlogsock('inet');
-      syslog('debug', "spamd starting");
-    };
-    $log_socket = 'inet' unless $@;
-  }
-
-  # fall back to stderr if all else fails
-  if ($@) {
-    warn "failed to setlogsock(${log_socket}) on this platform; reporting logs to stderr\n";
-    $log_facility = 'stderr';
-  }
-}
 
 my($port, $addr, $proto);
 my($listeninfo);             # just for reporting
@@ -486,9 +611,11 @@
 
     if ($got_sighup) {
       defined($opt{'pidfile'}) and unlink($opt{'pidfile'});
-  
+
       # leave Client fds active, and do not kill children; they can still
       # service clients until they exit.  But restart the listener anyway.
+      # And close the logfile, so the new instance can reopen it.
+      close(STDLOG) if $log_facility eq 'file';
       chdir($ORIG_CWD) || die "spamd restart failed: chdir failed: ${ORIG_CWD}: $!\n";
       exec ($ORIG_ARG0, @ORIG_ARGV);
       # should not get past that...
@@ -1187,6 +1314,7 @@
     }
 }
 
+
 sub logmsg
 {
   # install a new handler for SIGPIPE -- this signal has been
@@ -1200,42 +1328,56 @@
 
   warn "logmsg: $msg\n" if $opt{'debug'};
 
+  # log to file:
+  #   bug 1360 <http://bugzilla.spamassassin.org/show_bug.cgi?id=1360>
+  #   enable logging to a file via --syslog=file or --syslog=/path/to/file
   # log to STDERR:
-  # bug 605: http://bugzilla.spamassassin.org/show_bug.cgi?id=605
-  # more efficient for daemontools if --syslog=stderr is used
-  if ($log_facility eq 'stderr') {
-    print STDERR "$msg\n";
+  #   bug 605  <http://bugzilla.spamassassin.org/show_bug.cgi?id=605>
+  #   more efficient for daemontools if --syslog=stderr is used
+  if ($log_socket eq 'file') {
+    my @date = reverse((gmtime(time))[0 .. 5]);
+    $date[0] += 1900;
+    $date[1] +=    1;
+    syswrite(STDLOG,
+      sprintf("%04d-%02d-%02d %02d:%02d:%02d [%s] %s: %s\n",
+                @date,
+                $$,
+                'i',
+                $msg
+      )
+    );
   }
   # log to syslog (if logging isn't disabled completely via 'null')
-  elsif ($log_facility ne 'null') {
+  elsif ($log_socket ne 'none') {
     openlog('spamd', 'cons,pid', $log_facility);
 
     eval { syslog('info', "%s", $msg); };
 
     if ($@) {
-      warn "syslog() failed, try using --syslog-socket switch ($@)\n";
-    }
+      if ($main::SIGPIPE_RECEIVED) {
+        # SIGPIPE received when writing to syslog -- close and reopen
+        # the log handle, then try again.
+        closelog();
+        openlog('spamd', 'cons,pid', $log_facility);
+        syslog('info', "%s", $msg);
+
+        # now report what happend
+        $msg = "SIGPIPE received - reopening log socket";
+        warn "logmsg: $msg\n" if $opt{'debug'};
+        syslog('warning', "%s", $msg);
+
+        # if we've received multiple sigpipes, logging is probably
+        # still broken.
+        if ($main::SIGPIPE_RECEIVED > 1) {
+          warn "logging failure: multiple SIGPIPEs received\n";
+        }
 
-    if ($main::SIGPIPE_RECEIVED) {
-      # SIGPIPE recieved when writing to syslog -- close and reopen
-      # the log handle, then try again.
-
-      closelog();
-      openlog('spamd', 'cons,pid', $log_facility);
-      syslog('info', "%s", $msg);
-
-      # now report what happend
-      $msg = "SIGPIPE received - reopening log socket";
-      warn "logmsg: $msg\n" if $opt{'debug'};
-      syslog('warning', "%s", $msg);
-
-      # if we've received multiple sigpipes, logging is probably
-      # still broken.
-      if ($main::SIGPIPE_RECEIVED > 1) {
-        warn "logging failure: multiple SIGPIPEs received\n";
+        $main::SIGPIPE_RECEIVED = 0;
+      }
+      else {
+        warn "syslog() failed: $@\n" .
+             "try using --syslog-socket={unix,inet} or --syslog=file\n";
       }
-
-      $main::SIGPIPE_RECEIVED = 0;
     }
   }
 
@@ -1247,6 +1389,7 @@
     my ($sig) = @_;
     logmsg "server killed by SIG$sig, shutting down";
     $server->close;
+
     defined($opt{'pidfile'}) and unlink($opt{'pidfile'});
 
     # the UNIX domain socket
@@ -1599,8 +1742,23 @@
 =item B<-s> I<facility>, B<--syslog>=I<facility>
 
 Specify the syslog facility to use (default: mail).  If C<stderr> is specified,
-output will be written to stderr.  This is useful if you're running C<spamd>
-under the C<daemontools> package.
+output will be written to stderr. (This is useful if you're running C<spamd>
+under the C<daemontools> package.) With a I<facility> of C<file>, all output
+goes to spamd.log. I<facility> is interpreted as a file name to log to if it
+contains any characters except a-z and 0-9. C<null> disables logging completely
+(used internally).
+
+Examples:
+	spamd -s mail                 # use syslog, facility mail (default)
+	spamd -s ./mail               # log to file ./mail
+	spamd -s stderr 2>/dev/null   # log to stderr, throw messages away
+	spamd -s null                 # the same as above
+	spamd -s file                 # log to file ./spamd.log
+	spamd -s /var/log/spamd.log   # log to file /var/log/spamd.log
+
+If logging to a file is enabled and that log file is rotated, the spamd server
+must be restartet with a SIGHUP. (If the log file is just truncated, this is
+not needed but still recommended.)
 
 =item B<--syslog-socket>=I<type>
 
@@ -1612,6 +1770,8 @@
 the B<Sys::Syslog> package which do not support some socket types, so you may
 need to set this.  If you get error messages regarding B<__PATH_LOG> or similar
 from spamd, try changing this setting.
+
+The socket type C<file> is used internally and should not be specified.
 
 =item B<-u> I<username>, B<--username>=I<username>