python3/syslog:多个系统日志流?

Posted

技术标签:

【中文标题】python3/syslog:多个系统日志流?【英文标题】:python3/syslog: multiple syslog streams? 【发布时间】: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)。这是我能完成我想做的事情的唯一方法吗?

提前谢谢你。

【问题讨论】:

【参考方案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 是不可能的。

唯一的选择是

    使用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

    使用logging.handlers.SysLogHandler

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

【讨论】:

以上是关于python3/syslog:多个系统日志流?的主要内容,如果未能解决你的问题,请参考以下文章

点击流日志分析

解决 HttpServletRequest 流数据不可重复读

为什么要用 Kafka 呢

5分钟搭建轻量级日志系统Loki

消息中间件--kafka安装部署

kafka怎么收集到flume的日志