【问题标题】:python3/syslog: multiple syslog streams?python3/syslog:多个系统日志流?
【发布时间】:2017-11-11 18:25:20
【问题描述】:

我的 python3 程序有许多子模块,我希望它们每个都发送具有不同 syslog ident 值的 syslog 消息。例如,其中一个可能发送到myprogram/submod0,另一个可能发送到myprogram/submod1。我使用syslog-ng 将这些消息路由到不同的日志文件。

我想做的是这样的事情,我知道我现在在这里写它的方式是不可能的:

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

...然后,在submod0 内,我想发送这样的系统日志消息...

syslog0.syslog('some sort of message')

... 在submod1 内...

syslog1.syslog('another message')

但是,当然,syslog.openlog 不会返回任何可以通过这种方式用作句柄的对象。

有什么方法可以使用 python3 的syslog 工具完成我想要的吗?

我想我可以为每条要发送的系统日志消息发出一个新的openlog。比如……

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

...然后在我的submod0 中使用mysyslog('submod0', message),在我的submod1 中使用mysyslog('submod1', message)。这是我能完成我想做的事情的唯一方法吗?

提前谢谢你。

【问题讨论】:

    标签: python-3.x syslog


    【解决方案1】:

    好的。我看到我可以通过logging.handlers.SysLogHandler 做到这一点...

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

    这是对我问题的回答,但并不理想,因为我尽量避免使用 logging 模块。

    我将继续寻找另一种方法来做到这一点。

    【讨论】:

    • 你找到其他方法了吗?
    【解决方案2】:

    我找不到任何现有的 python 模块来做我想做的事,所以我决定编写自己的 syslog 包装器。它被写入基于host:port 或套接字文件(如/dev/log)打开系统日志连接,然后接受所有其他参数,如facilityseverityprogram 等。调用以发送系统日志消息。

    使用单个日志记录方法调用级别的所有参数,可以将此类包装在更高级别以提供句柄,例如,通过program 的唯一连接,这是我在原始文件中指定的在这里提问。

    我只用 python3.6 和/dev/log 案例测试了以下代码。它对我有用,但使用它需要您自担风险。

    #!/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
    

    【讨论】:

      【解决方案3】:

      这对于 Python 的 syslog 是不可能的。

      唯一的选择是

      1. 使用syslog,调用openlog(ident)切换标识符

      CPython 的 syslog 包装了 unix 库 syslog (#include &lt;syslog.h&gt;)。那里存在相同的限制...您可以将ident 设置为openlog(ident),但不能在使用syslog() 发送消息时设置。也许unix lib允许不同标识符的多个“记录器”,我不知道......但绝对不是Python的syslog

      您可以在此处查看 CPython 对所有这些的实现:https://github.com/python/cpython/blob/92a98ed/Modules/syslogmodule.c

      1. 使用logging.handlers.SysLogHandler

      SysLogHandler 很不错。您可以使用自己的 ident 值创建不同的处理程序。它处理格式化。并且它会设置你想要发送消息的任何套接字(UDP、TCP 或 unix 域)。

      【讨论】:

        猜你喜欢
        • 2012-11-10
        • 2011-07-24
        • 2011-01-03
        • 2014-04-30
        • 2013-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多