# -*- Mode: Python; tab-width: 4 -*-

# From: "Samual M. Rushing" <rushing@nightmare.com>
# Date: Mon, 21 Apr 1997 16:10:42 -0500

# ======================================================================
# Copyright 1997 by Sam Rushing
#
#                         All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Sam
# Rushing not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# ======================================================================

"""socket interface to unix syslog.
On Unix, there are usually two ways of getting to syslog: via a
local unix-domain socket, or via the TCP service.

Usually "/dev/log" is the unix domain socket.  This may be different
for other systems.

>>> my_client = syslog_client ('/dev/log')

Otherwise, just use the UDP version, port 514.

>>> my_client = syslog_client (('my_log_host', 514))

On win32, you will have to use the UDP version.  Note that
you can use this to log to other hosts (and indeed, multiple
hosts).

This module is not a drop-in replacement for the python
<syslog> extension module - the interface is different.

Usage:

>>> c = syslog_client()
>>> c = syslog_client ('/strange/non_standard_log_location')
>>> c = syslog_client (('other_host.com', 514))
>>> c.log ('testing', facility='local0', priority='debug')

"""

# TODO: support named-pipe syslog.
# [see ftp://sunsite.unc.edu/pub/Linux/system/Daemons/syslog-fifo.tar.z]

# from <linux/sys/syslog.h>:
# ===========================================================================
# priorities/facilities are encoded into a single 32-bit quantity, where the
# bottom 3 bits are the priority (0-7) and the top 28 bits are the facility
# (0-big number).  Both the priorities and the facilities map roughly
# one-to-one to strings in the syslogd(8) source code.  This mapping is
# included in this file.
#
# priorities (these are ordered)

LOG_EMERG       = 0     #  system is unusable
LOG_ALERT       = 1     #  action must be taken immediately
LOG_CRIT        = 2     #  critical conditions
LOG_ERR         = 3     #  error conditions
LOG_WARNING     = 4     #  warning conditions
LOG_NOTICE      = 5     #  normal but significant condition
LOG_INFO        = 6     #  informational
LOG_DEBUG       = 7     #  debug-level messages

#  facility codes
LOG_KERN        = 0     #  kernel messages
LOG_USER        = 1     #  random user-level messages
LOG_MAIL        = 2     #  mail system
LOG_DAEMON      = 3     #  system daemons
LOG_AUTH        = 4     #  security/authorization messages
LOG_SYSLOG      = 5     #  messages generated internally by syslogd
LOG_LPR         = 6     #  line printer subsystem
LOG_NEWS        = 7     #  network news subsystem
LOG_UUCP        = 8     #  UUCP subsystem
LOG_CRON        = 9     #  clock daemon
LOG_AUTHPRIV    = 10    #  security/authorization messages (private)

#  other codes through 15 reserved for system use
LOG_LOCAL0      = 16        #  reserved for local use
LOG_LOCAL1      = 17        #  reserved for local use
LOG_LOCAL2      = 18        #  reserved for local use
LOG_LOCAL3      = 19        #  reserved for local use
LOG_LOCAL4      = 20        #  reserved for local use
LOG_LOCAL5      = 21        #  reserved for local use
LOG_LOCAL6      = 22        #  reserved for local use
LOG_LOCAL7      = 23        #  reserved for local use

priority_names = {
    "alert":    LOG_ALERT,
    "crit":     LOG_CRIT,
    "debug":    LOG_DEBUG,
    "emerg":    LOG_EMERG,
    "err":      LOG_ERR,
    "error":    LOG_ERR,        #  DEPRECATED
    "info":     LOG_INFO,
    "notice":   LOG_NOTICE,
    "panic":    LOG_EMERG,      #  DEPRECATED
    "warn":     LOG_WARNING,        #  DEPRECATED
    "warning":  LOG_WARNING,
    }

facility_names = {
    "auth":     LOG_AUTH,
    "authpriv": LOG_AUTHPRIV,
    "cron":     LOG_CRON,
    "daemon":   LOG_DAEMON,
    "kern":     LOG_KERN,
    "lpr":      LOG_LPR,
    "mail":     LOG_MAIL,
    "news":     LOG_NEWS,
    "security": LOG_AUTH,       #  DEPRECATED
    "syslog":   LOG_SYSLOG,
    "user":     LOG_USER,
    "uucp":     LOG_UUCP,
    "local0":   LOG_LOCAL0,
    "local1":   LOG_LOCAL1,
    "local2":   LOG_LOCAL2,
    "local3":   LOG_LOCAL3,
    "local4":   LOG_LOCAL4,
    "local5":   LOG_LOCAL5,
    "local6":   LOG_LOCAL6,
    "local7":   LOG_LOCAL7,
    }

import socket

class syslog_client:
    def __init__ (self, address='/dev/log'):
        self.address = address
        if type (address) == type(''):
            try: # APUE 13.4.2 specifes /dev/log as datagram socket
                self.socket = socket.socket( socket.AF_UNIX
                                                       , socket.SOCK_DGRAM)
                self.socket.connect (address)
            except: # older linux may create as stream socket
                self.socket = socket.socket( socket.AF_UNIX
                                                       , socket.SOCK_STREAM)
                self.socket.connect (address)
            self.unix = 1
        else:
            self.socket = socket.socket( socket.AF_INET
                                                   , socket.SOCK_DGRAM)
            self.unix = 0

    # curious: when talking to the unix-domain '/dev/log' socket, a
    #   zero-terminator seems to be required.  this string is placed
    #   into a class variable so that it can be overridden if
    #   necessary.

    log_format_string = '<%d>%s\000'

    def log (self, message, facility=LOG_USER, priority=LOG_INFO):
        message = self.log_format_string % (
            self.encode_priority (facility, priority),
            message
            )
        if self.unix:
            self.socket.send (message)
        else:
            self.socket.sendto (message, self.address)

    def encode_priority (self, facility, priority):
        if type(facility) == type(''):
            facility = facility_names[facility]
        if type(priority) == type(''):
            priority = priority_names[priority]
        return (facility<<3) | priority

    def close (self):
        if self.unix:
            self.socket.close()

if __name__ == '__main__':
    """
       Unit test for syslog_client.  Set up for the test by:

    * tail -f /var/log/allstuf (to see the "normal" log messages).

        * Running the test_logger.py script with a junk file name (which
      will be opened as a Unix-domain socket). "Custom" log messages
      will go here.

    * Run this script, passing the same junk file name.

    * Check that the "bogus" test throws, and that none of the rest do.

    * Check that the 'default' and 'UDP' messages show up in the tail.

    * Check that the 'non-std' message shows up in the test_logger
      console.

    * Finally, kill off the tail and test_logger, and clean up the
      socket file.
    """
    import sys, traceback

    if len( sys.argv ) != 2:
        print "Usage: syslog.py localSocketFilename"
        sys.exit()

    def test_client( desc, address=None ):
        try:
            if address:
                client = syslog_client( address )
            else:
                client = syslog_client()
        except:
            print 'syslog_client() [%s] ctor threw' % desc
            traceback.print_exc()
            return

        try:
            client.log( 'testing syslog_client() [%s]' % desc
                      , facility='local0', priority='debug' )
            print 'syslog_client.log() [%s] did not throw' % desc
        except:
            print 'syslog_client.log() [%s] threw' % desc
            traceback.print_exc()

    test_client( 'default' )
    test_client( 'bogus file', '/some/bogus/logsocket' )
    test_client( 'nonstd file', sys.argv[1] )
    test_client( 'UDP',  ('localhost', 514) )
