多行日志记录在Syslog中的处理方法

39

我已经使用Python的SysLogHandler配置了我的Python应用程序以记录到syslog,并且一切都正常。除了多行处理。并不是我非常需要发出多行日志记录(我确实需要一点),而是我需要能够读取Python的异常信息。我正在使用带有rsyslog 4.2.0的Ubuntu。以下是我得到的:

Mar 28 20:11:59 telemachos root: ERROR 'EXCEPTION'#012Traceback (most recent call last):#012  File "./test.py", line 22, in <module>#012    foo()#012  File "./test.py", line 13, in foo#012    bar()#012  File "./test.py", line 16, in bar#012    bla()#012  File "./test.py", line 19, in bla#012    raise Exception("EXCEPTION!")#012Exception: EXCEPTION!

如果您需要测试代码,请参考以下内容:

import logging
from logging.handlers import SysLogHandler

logger = logging.getLogger()
logger.setLevel(logging.INFO)
syslog = SysLogHandler(address='/dev/log', facility='local0')
formatter = logging.Formatter('%(name)s: %(levelname)s %(message)r')
syslog.setFormatter(formatter)
logger.addHandler(syslog)

def foo():
    bar()

def bar():
    bla()

def bla():
    raise Exception("EXCEPTION!")

try:
    foo()
except:
    logger.exception("EXCEPTION")
3个回答

39

另外,如果您想在解析日志时保持syslog的完整性,可以在查看日志时仅替换字符。

tail -f /var/log/syslog | sed 's/#012/\n\t/g'

2
sed 不起作用,会得到 "blah blah line1 nt blah blah line2"。然而 perl 起作用:tail -f /var/log/syslog | perl -pe 's/#012/\n\t/g;' - user9645

36

好的,最终我弄清楚了...

rsyslog默认转义所有奇怪的字符(ASCII < 32),其中包括换行符(以及制表符和其他字符)。

$EscapeControlCharactersOnReceive:

此指令指示rsyslogd在接收消息时替换控制字符。其目的是提供一种方法来阻止非可打印消息作为整体进入syslog系统。如果启用此选项,则所有控制字符都将转换为3位八进制数,并在前面加上$ControlCharacterEscapePrefix字符(默认情况下为“\”)。例如,如果消息中包含BEL字符(ctrl-g),则它将被转换为“\007”。

您可以将以下内容添加到rsyslog配置中以关闭它:

$EscapeControlCharactersOnReceive off

或者,使用“新”的高级语法:

global(parser.escapeControlCharactersOnReceive="off")

10
这个方法可能对你的情况有效,但如果你正在编写高并发量的应用程序,@Nick的答案将使查找所有与堆栈跟踪相关的行变得更加容易。你的解决方案将失去第一行后所有行的应用程序名称和级别名称,导致搜索困难。 - mattbornski
@mattbornski,您不会丢失任何信息,只是它不会在同一行上。 - Marwan Alsabbagh
16
“失去”在这里的意思是“无法重建”。在高容量应用程序中,将日志的含义分散到多行中使得无法重新组合,因为来自多个请求的行交织在一起。 - mattbornski
@mattbornski 他们怎么可能交错呢?我在考虑每个延续行都缩进4个空格的想法。这样,所有以非空白字符开头的行都将成为日志行,而以空白字符开头的每一行都将成为最近日志行的延续。此外,这种方式非常易读。但是,如果记录有可能交错,那么这种方法就行不通了(?) - Hubro
2
通常情况下,您需要将来自多个线程/进程/节点的日志发送到较少数量的收集器。在这种情况下,收集器必须以某种方式交错记录(逐行读取、轮询?读取直到没有更多数据、轮询?)。 - mattbornski
1
如果其他人使用这个系统,千万不要 永远 这样做。这会完全打破syslog中日志条目应该在单行中的预期。如果你在我管理的任何一个系统上这样做,我会召集其他管理员一起前来带着火炬和干草叉,告诉你撤销更改。每10分钟增加一层管理,直到你撤销更改为止。你不能对其他人使用的关键基础设施做出这样的事情。 - Andrew Henle

4

另一种选择是对SysLogHandler进行子类化并重写emit()方法-然后您可以为收到的每行文本调用超类的emit()方法。例如:

from logging import LogRecord
from logging.handlers import SysLogHandler

class MultilineSysLogHandler(SysLogHandler):
    def emit(self, record):
        if '\n' in record.msg:
            record_args = [record.args] if isinstance(record.args, dict) else record.args
            for single_line in record.msg.split('\n'):
                single_line_record = LogRecord(
                    name=record.name,
                    level=record.levelno,
                    pathname=record.pathname,
                    msg=single_line,
                    args=record_args,
                    exc_info=record.exc_info,
                    func=record.funcName
                )
                super(MultilineSysLogHandler, self).emit(single_line_record)
        else:
            super(MultilineSysLogHandler, self).emit(record)

1
应将 level= 改为 levelno=,并且遗漏了 lineno=record.lineno。但是,如果消息在 \n 后带有 %s 等内容,则参数替换将无法正常工作。在分割之前,此代码需要完全参数化消息,但仅当 record.levelno 通过过滤器时。 - Terris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接