2

My python3 program has a number of submodules, and I want them each to send syslog messages with a different syslog ident value. For example, one of them might send to myprogram/submod0, and another one might send to myprogram/submod1. I use syslog-ng to route these messages to different log files.

What I'd like to do is something like this, which I know is not currently possible in the way that I'm writing it here:

syslog0 = syslog.openlog('myprogram/submod0', 
                         syslog.LOG_PID, syslog.LOG_MAIL)
syslog1 = syslog.openlog('myprogram/submod1',
                         syslog.LOG_PID, syslog.LOG_MAIL)

... and then, within submod0, I want to send syslog messages like this ...

syslog0.syslog('some sort of message')

... and this way within submod1 ...

syslog1.syslog('another message')

But, of course, syslog.openlog doesn't return any kind of object that I can use as a handle in this way.

Is there any way that I can accomplish what I want using the syslog facilities of python3?

I suppose that I could issue a new openlog for each syslog message that I want to send. For example ...

def mysyslog(ident, message):
    syslog.openlog('myprogram/{}'.format(ident),
                   syslog.LOG_PID, syslog.LOG_MAIL)
    syslog.syslog(message)

... and then use mysyslog('submod0', message) within my submod0 and mysyslog('submod1', message) within my submod1. Is this the only way I can accomplish what I want to do?

Thank you in advance.

3 Answers 3

1

OK. I see that I can do this via logging.handlers.SysLogHandler ...

https://docs.python.org/3/library/logging.handlers.html#sysloghandler

This is an answer to my question, but it isn't ideal, because I am trying to avoid using the logging module, if at all possible.

I'm going to keep searching for another way to do this.

Sign up to request clarification or add additional context in comments.

1 Comment

did you figure out another way?
0

I couldn't find any existing python modules which do what I want, so I decided to write my own syslog wrapper. It is written to open a syslog connection based on either host:port or a socket file such as /dev/log, and to then accept all of the other parameters like facility, severity, program, etc. on each call to send a syslog message.

With all the parameters at the level of the individual logging method call, this class could be wrapped at a higher level to provide handles to, for example, unique connections via program, which is what I have specified in my original question here.

I have only tested the following code with python3.6 and in the /dev/log case. It works for me, but use it at your own risk.

#!/usr/bin/python3.6

import os
import sys
import socket
import datetime

# Only imported for the syslog.LOG_INFO and syslog.LOG_USER constants.
import syslog

# Appends a newline in all cases.
def _log_stderr(message):
    if message:
        sys.stderr.write(message)
    sys.stderr.write('\n')
    sys.stderr.flush()

# XSyslog: a syslog wrapper class.
#
# This module allows the facility (such as LOG_USER), the
# severity (such as LOG_INFO), and other syslog parameters
# to be set on a message-by-message basis via one, single
# syslog connection.
#
# Usage:
#
#   slog = XSyslog([server=server], [port=port], [proto=proto],
#                  [clientname=clientname], [maxlen=maxlen])
#
# This allows three  cases:
# (1) Connect to syslog via UDP to a host and port:
#     Specify host, port, and proto='UDP'.
# (2) Connect to syslog via TCP to a host and port:
#     Specify host, port, and proto='TCP'.
# (3) Connect to syslog via a socket file such as /dev/log.
#     Specify proto=filename (e.g., proto='/dev/log').
#     In this case, host and port are ignored.
#
# clientname is an optional field for the syslog message.
# maxlen is the maximum message length.
#
# Once the XSyslog object is created, the message can be sent as follows:
#
#   slog = XSyslog([... parameters ...])
#   slog.log(message, [facility=facility], [severity=severity],
#                     [timestamp=timestamp], [hostame=hostname],
#                     [program=program], [pid=pid])
#     facility  defaults to LOG_USER
#     severity  defaults to LOG_INFO
#     timestamp defaults to now
#     hostname  if None, use clientname if it exists; if '', no hostname.
#     program   defaults to "logger"
#     pid       defaults to os.getpid()

class XSyslog(object):

    def __init__(self, server=None, port=None, proto='udp', clientname=None, maxlen=1024):
        self.server       = server
        self.port         = port
        self.proto        = socket.SOCK_DGRAM
        self.clientname   = None
        self.maxlen       = maxlen
        self._protoname   = ''
        self._socket      = None
        self._sockfile    = None
        self._connectargs = ()
        self._me          = os.path.splitext(self.__class__.__name__)[1][1:]

        if proto:
            if proto.lower() == 'udp':
                self._protoname  = proto.lower()
                self.proto       = socket.SOCK_DGRAM
                self._socketargs = (self.server, self.port, socket.AF_UNSPEC, self.proto)
            elif proto.lower() == 'tcp':
                self._protoname  = proto.lower()
                self.proto       = socket.SOCK_STREAM
                self._socketargs = (self.server, self.port, socket.AF_UNSPEC, self.proto)
            elif len(proto) > 0:
                self._sockfile   = proto
                self._protoname  = self._sockfile
                self.proto       = socket.SOCK_DGRAM
                self._socketargs = (socket.AF_UNIX, self.proto)

        badargs = False
        if self._sockfile:
            pass
        elif self.server and self.port:
            pass
        else:
            badargs = True
        if not self.proto:
            badargs = True
        if badargs:
            raise ValueError("'proto' must be 'udp' or 'tcp' with 'server' and 'port', or else a socket filename like '/dev/log'")

        if not self.clientname:
            try:
                self.clientname = socket.getfqdn()
                if not self.clientname:
                    self.clientname = socket.gethostname()
            except:
                self.clientname = None

    def _connect(self):
        if self._socket is None:
            if self._sockfile:
                self._socket = socket.socket(*self._socketargs)
                if not self._socket:
                    _log_stderr(':::::::: {}: unable to open socket file {}'.format(self._me, self._sockfile))
                    return False
                try:
                    self._socket.connect(self._sockfile)
                    return True
                except socket.timeout as e:
                    _log_stderr(':::::::: {}: sockfile timeout e={}'.format(self._me, e))
                    # Force-close the socket and its contained fd, to avoid fd leaks.
                    self.close()
                except socket.error as e:
                    _log_stderr(':::::::: {}: sockfile error f={}, e={}'.format(self._me, self._sockfile, e))
                    # Force-close the socket and its contained fd, to avoid fd leaks.
                    self.close()
                except Exception as e:
                    # Any other exception which might occur ...
                    _log_stderr(':::::::: {}: sockfile exception f={}, e={}'.format(self._me, self._sockfile, e))
                    # Force-close the socket and its contained fd, to avoid fd leaks.
                    self.close()
                return False
            else:
                addrinfo = socket.getaddrinfo(*self._socketargs)
                if addrinfo is None:
                    return False
                # Check each socket family member until we find one we can connect to.
                for (addr_fam, sock_kind, proto, ca_name, sock_addr) in addrinfo:
                    self._socket = socket.socket(addr_fam, self.proto)
                    if not self._socket:
                        _log_stderr(':::::::: {}: unable to get a {} socket'.format(self._me, self._protoname))
                        return False
                    try:
                        self._socket.connect(sock_addr)
                        return True
                    except socket.timeout as e:
                        _log_stderr(':::::::: {}: {} timeout e={}'.format(self.me, self._protoname, e))
                        # Force-close the socket and its contained fd, to avoid fd leaks.
                        self.close()
                        continue
                    except socket.error as e:
                        _log_stderr(':::::::: {}: {} error e={}'.format(self._me, self._protoname, e))
                        # Force-close the socket and its contained fd, to avoid fd leaks.
                        self.close()
                        continue
                    except Exception as e:
                        # Any other exception which might occur ...
                        _log_stderr(':::::::: {}: {} exception e={}'.format(self._me, self._protoname, e))
                        # Force-close the socket and its contained fd, to avoid fd leaks.
                        self.close()
                        continue
                # Force-close the socket and its contained fd, to avoid fd leaks.
                self.close()
                return False
        else:
            return True

    def close(self):
        try:
            self._socket.close()
        except:
            pass
        self._socket = None

    def log(self, message, facility=None, severity=None, timestamp=None, hostname=None, program=None, pid=None):

        if message is None:
            return

        if not facility:
            facility = syslog.LOG_USER

        if not severity:
            severity = syslog.LOG_INFO

        pri = facility + severity

        data = '<{}>'.format(pri)

        if timestamp:
            t = timestamp
        else:
            t = datetime.datetime.now()
        data = '{}{}'.format(data, t.strftime('%Y-%m-%dT%H:%M:%S.%f'))

        if hostname is None:
            if self.clientname:
                data = '{} {}'.format(data, self.clientname)
        elif not hostname:
            # For hostname == '', leave out the hostname, altogether.
            pass
        else:
            data = '{} {}'.format(data, hostname)

        if program:
            data = '{} {}'.format(data, program)
        else:
            data = '{} logger'.format(data)

        if not pid:
            pid = os.getpid()

        data = '{}[{}]: {}'.format(data, pid, message).encode('ascii', 'ignore')

        if not self._socket:
            self._connect()

        if not self._socket:
            raise Exception('{}: unable to connect to {} syslog via {}'.format(self._me, self._protoname, self._socketargs))
        try:
            if self.maxlen:
                self._socket.sendall(data[:self.maxlen])
            else:
                self._socket.sendall(data)
        except IOError as e:
            _log_stderr(':::::::: {}: {} syslog io error {} via {}'.format(self._me, self._protoname, e, self._socketargs))
            self.close()
            raise
        except Exception as e:
            # Any other exception which might occur ...
            _log_stderr(':::::::: {}: {} syslog exception {} via {}'.format(self._me, self._protoname, e, self._socketargs))
            self.close()
            raise

Comments

0

this is not possible with Python's syslog.

the only options are

  1. use syslog, call openlog(ident) to switch identifiers

CPython's syslog wraps the unix library syslog (#include <syslog.h>). The same limitation exists there... you can set ident with openlog(ident) but not when sending messages with syslog(). Maybe the unix lib allows multiple "loggers" for different identifiers, i don't know... but definitely not Python's syslog.

You can see CPython's implementation of all this here: https://github.com/python/cpython/blob/92a98ed/Modules/syslogmodule.c

  1. use logging.handlers.SysLogHandler

SysLogHandler is quite nice. You can make different handlers with their own ident values. It handles formatting. And it sets up whatever socket you want to send the messages over (UDP, TCP, or unix domain).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.