[Zope-Checkins] CVS: Zope3/lib/python/Zope/Server/Logger - FileLogger.py:1.2 ILogger.py:1.2 MultiLogger.py:1.2 ResolvingLogger.py:1.2 RotatingFileLogger.py:1.2 SocketLogger.py:1.2 SyslogLogger.py:1.2 TailLogger.py:1.2 UnresolvingLogger.py:1.2 __init__.py:1.2 m_syslog.py:1.2

Jim Fulton jim@zope.com
Mon, 10 Jun 2002 19:30:08 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/Server/Logger
In directory cvs.zope.org:/tmp/cvs-serv20468/lib/python/Zope/Server/Logger

Added Files:
	FileLogger.py ILogger.py MultiLogger.py ResolvingLogger.py 
	RotatingFileLogger.py SocketLogger.py SyslogLogger.py 
	TailLogger.py UnresolvingLogger.py __init__.py m_syslog.py 
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.

=== Zope3/lib/python/Zope/Server/Logger/FileLogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from types import StringType
+
+from ILogger import ILogger
+
+
+class FileLogger:
+    """Simple File Logger
+    """
+
+    __implements__ = ILogger
+
+
+    def __init__ (self, file, flush=1, mode='a'):
+        """pass this either a path or a file object."""
+        if type(file) is StringType:
+            if (file == '-'):
+                import sys
+                self.file = sys.stdout
+            else:
+                self.file = open (file, mode)
+        else:
+            self.file = file
+        self.do_flush = flush
+
+
+    def __repr__ (self):
+        return '<file logger: %s>' % self.file
+
+
+    def write (self, data):
+        self.file.write (data)
+        self.maybe_flush()
+
+
+    def writeline (self, line):
+        self.file.writeline (line)
+        self.maybe_flush()
+
+
+    def writelines (self, lines):
+        self.file.writelines (lines)
+        self.maybe_flush()
+
+
+    def maybe_flush (self):
+        if self.do_flush:
+            self.file.flush()
+
+
+    def flush (self):
+        self.file.flush()
+
+
+    def softspace (self, *args):
+        pass
+
+
+    ############################################################
+    # Implementation methods for interface
+    # Zope.Server.Logger.ILogger
+
+    def log(self, message):
+        'See Zope.Server.Logger.ILogger.ILogger'
+        if message[-1] not in ('\r', '\n'):
+            self.write (message + '\n')
+        else:
+            self.write (message)
+
+    #
+    ############################################################


=== Zope3/lib/python/Zope/Server/Logger/ILogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from Interface import Interface
+
+
+class ILogger(Interface):
+    """This interface describes the methods any Logging object has to
+       implement.
+    """
+
+    def log(message):
+        """Logs the passed message at the appropriate place."""


=== Zope3/lib/python/Zope/Server/Logger/MultiLogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import asynchat
+import socket
+import time         # these three are for the rotating logger
+import os           # |
+import stat         # v
+
+from types import StringType
+
+
+class MultiLogger:
+    """Log to multiple places."""
+
+    def __init__ (self, loggers):
+        self.loggers = loggers
+
+    def __repr__ (self):
+        return '<multi logger: %s>' % (repr(self.loggers))
+
+    def log (self, message):
+        for logger in self.loggers:
+            logger.log (message)
+
+
+
+class ResolvingLogger:
+    """Feed (ip, message) combinations into this logger to get a
+    resolved hostname in front of the message.  The message will not
+    be logged until the PTR request finishes (or fails)."""
+
+    def __init__ (self, resolver, logger):
+        self.resolver = resolver
+        self.logger = logger
+
+
+    class logger_thunk:
+        def __init__ (self, message, logger):
+            self.message = message
+            self.logger = logger
+
+        def __call__ (self, host, ttl, answer):
+            if not answer:
+                answer = host
+            self.logger.log ('%s%s' % (answer, self.message))
+
+
+    def log (self, ip, message):
+        self.resolver.resolve_ptr (
+                ip,
+                self.logger_thunk (
+                        message,
+                        self.logger
+                        )
+                )
+
+
+
+class UnresolvingLogger:
+    """Just in case you don't want to resolve"""
+    def __init__ (self, logger):
+        self.logger = logger
+
+    def log (self, ip, message):
+        self.logger.log ('%s%s' % (ip, message))
+
+
+def strip_eol (line):
+    while line and line[-1] in '\r\n':
+        line = line[:-1]
+    return line
+
+
+class TailLogger:
+    """Keep track of the last <size> log messages"""
+    def __init__ (self, logger, size=500):
+        self.size = size
+        self.logger = logger
+        self.messages = []
+
+    def log (self, message):
+        self.messages.append (strip_eol (message))
+        if len (self.messages) > self.size:
+            del self.messages[0]
+        self.logger.log (message)


=== Zope3/lib/python/Zope/Server/Logger/ResolvingLogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from ILogger import ILogger
+
+
+class ResolvingLogger:
+    """Feed (ip, message) combinations into this logger to get a
+    resolved hostname in front of the message.  The message will not
+    be logged until the PTR request finishes (or fails)."""
+
+    __implements__ = ILogger
+
+    def __init__ (self, resolver, logger):
+        self.resolver = resolver
+        self.logger = logger
+
+
+    class logger_thunk:
+        def __init__ (self, message, logger):
+            self.message = message
+            self.logger = logger
+
+        def __call__ (self, host, ttl, answer):
+            if not answer:
+                answer = host
+            self.logger.log ('%s: %s' % (answer, self.message))
+
+
+    ############################################################
+    # Implementation methods for interface
+    # Zope.Server.Logger.ILogger
+
+    def log(self, ip, message):
+        'See Zope.Server.Logger.ILogger.ILogger'
+        self.resolver.resolve_ptr (
+                ip,
+                self.logger_thunk (
+                        message,
+                        self.logger
+                        )
+                )
+    #
+    ############################################################


=== Zope3/lib/python/Zope/Server/Logger/RotatingFileLogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import time
+import os
+import stat
+
+from FileLogger import FileLogger
+
+class RotatingFileLogger(FileLogger):
+    """ If freq is non-None we back up 'daily', 'weekly', or
+        'monthly'.  Else if maxsize is non-None we back up whenever
+        the log gets to big.  If both are None we never back up.
+
+        Like a FileLogger, but it must be attached to a filename.
+        When the log gets too full, or a certain time has passed, it
+        backs up the log and starts a new one.  Note that backing up
+        the log is done via 'mv' because anything else (cp, gzip)
+        would take time, during which medusa would do nothing else.
+    """
+
+    __implements__ = FileLogger.__implements__
+
+
+    def __init__ (self, file, freq=None, maxsize=None, flush=1, mode='a'):
+        self.filename = file
+        self.mode = mode
+        self.file = open (file, mode)
+        self.freq = freq
+        self.maxsize = maxsize
+        self.rotate_when = self.next_backup(self.freq)
+        self.do_flush = flush
+
+
+    def __repr__ (self):
+        return '<rotating-file logger: %s>' % self.file
+
+
+    # We back up at midnight every 1) day, 2) monday, or 3) 1st of month
+    def next_backup (self, freq):
+        (yr, mo, day, hr, min, sec, wd, jday, dst) = \
+             time.localtime(time.time())
+        if freq == 'daily':
+            return time.mktime((yr,mo,day+1, 0,0,0, 0,0,-1))
+        elif freq == 'weekly':
+            # wd(monday)==0
+            return time.mktime((yr,mo,day-wd+7, 0,0,0, 0,0,-1))
+        elif freq == 'monthly':
+            return time.mktime((yr,mo+1,1, 0,0,0, 0,0,-1))
+        else:
+            return None                  # not a date-based backup
+
+
+    def maybe_flush (self):              # rotate first if necessary
+        self.maybe_rotate()
+        if self.do_flush:                # from file_logger()
+            self.file.flush()
+
+
+    def maybe_rotate (self):
+        if self.freq and time.time() > self.rotate_when:
+            self.rotate()
+            self.rotate_when = self.next_backup(self.freq)
+        elif self.maxsize:               # rotate when we get too big
+            try:
+                if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:
+                    self.rotate()
+            except os.error:             # file not found, probably
+                self.rotate()            # will create a new file
+
+
+    def rotate (self):
+        (yr, mo, day, hr, min, sec, wd, jday, dst) = \
+             time.localtime(time.time())
+        try:
+            self.file.close()
+            newname = '%s.ends%04d%02d%02d' % (self.filename, yr, mo, day)
+            try:
+                open(newname, "r").close()      # check if file exists
+                newname = newname + "-%02d%02d%02d" % (hr, min, sec)
+            except:                             # YEARMODY is unique
+                pass
+            os.rename(self.filename, newname)
+            self.file = open(self.filename, self.mode)
+        except:
+            pass


=== Zope3/lib/python/Zope/Server/Logger/SocketLogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import asynchat
+import socket
+
+from ILogger import ILogger
+
+
+class SocketLogger (asynchat.async_chat):
+    """Log to a stream socket, asynchronously."""
+
+    __implements__ = ILogger
+
+    def __init__ (self, address):
+
+        if type(address) == type(''):
+            self.create_socket (socket.AF_UNIX, socket.SOCK_STREAM)
+        else:
+            self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+        self.connect (address)
+        self.address = address
+
+
+    def __repr__ (self):
+        return '<socket logger: address=%s>' % (self.address)
+
+
+    ############################################################
+    # Implementation methods for interface
+    # Zope.Server.Logger.ILogger
+
+    def log(self, message):
+        'See Zope.Server.Logger.ILogger.ILogger'
+        if message[-2:] != '\r\n':
+            self.socket.push (message + '\r\n')
+        else:
+            self.socket.push (message)
+
+    #
+    ############################################################


=== Zope3/lib/python/Zope/Server/Logger/SyslogLogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import os
+import m_syslog
+
+from ILogger import ILogger
+
+
+class SyslogLogger(m_syslog.syslog_client):
+    """syslog is a line-oriented log protocol - this class would be
+       appropriate for FTP or HTTP logs, but not for dumping stderr
+       to.
+
+       XXX: a simple safety wrapper that will ensure that the line
+       sent to syslog is reasonable.
+
+       XXX: async version of syslog_client: now, log entries use
+       blocking send()
+    """
+
+    __implements__ = ILogger
+
+    svc_name = 'zope'
+    pid_str  = str(os.getpid())
+
+    def __init__ (self, address, facility='user'):
+        m_syslog.syslog_client.__init__ (self, address)
+        self.facility = m_syslog.facility_names[facility]
+        self.address=address
+
+
+    def __repr__ (self):
+        return '<syslog logger address=%s>' % (repr(self.address))
+
+
+    ############################################################
+    # Implementation methods for interface
+    # Zope.Server.Logger.ILogger
+
+    def log(self, message):
+        'See Zope.Server.Logger.ILogger.ILogger'
+        m_syslog.syslog_client.log (
+            self,
+            '%s[%s]: %s' % (self.svc_name, self.pid_str, message),
+            facility=self.facility,
+            priority=m_syslog.LOG_INFO
+            )
+
+    #
+    ############################################################


=== Zope3/lib/python/Zope/Server/Logger/TailLogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from ILogger import ILogger
+
+
+class TailLogger:
+    """Keep track of the last <size> log messages"""
+
+    __implements__ = ILogger
+
+    def __init__ (self, logger, size=500):
+        self.size = size
+        self.logger = logger
+        self.messages = []
+
+
+    ############################################################
+    # Implementation methods for interface
+    # Zope.Server.Logger.ILogger
+
+    def log(self, message):
+        'See Zope.Server.Logger.ILogger.ILogger'
+        self.messages.append (strip_eol (message))
+        if len (self.messages) > self.size:
+            del self.messages[0]
+        self.logger.log (message)
+
+    #
+    ############################################################
+
+
+def strip_eol (line):
+    while line and line[-1] in '\r\n':
+        line = line[:-1]
+    return line


=== Zope3/lib/python/Zope/Server/Logger/UnresolvingLogger.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+from ILogger import ILogger
+
+
+class UnresolvingLogger:
+    """Just in case you don't want to resolve"""
+
+    __implements__ = ILogger
+
+    def __init__ (self, logger):
+        self.logger = logger
+
+
+    ############################################################
+    # Implementation methods for interface
+    # Zope.Server.Logger.ILogger
+
+    def log(self, ip, message):
+        'See Zope.Server.Logger.ILogger.ILogger'
+        self.logger.log ('%s: %s' % (ip, message))
+
+    #
+    ############################################################


=== Zope3/lib/python/Zope/Server/Logger/__init__.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""


=== Zope3/lib/python/Zope/Server/Logger/m_syslog.py 1.1 => 1.2 ===
+
+# ======================================================================
+# 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
+
+
+    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()
+