如何使用structlog添加代码行号

Ran*_*ent 9 structlog

使用 python 标准日志模块,可以使用以下方法添加原始日志调用的行号: %(lineno)s.

这如何使用 structlog 来完成?

vit*_*lkv 10

根据官方文档,你可以添加

structlog.configure(
        processors=[
            # ...
            # Add callsite parameters.
            structlog.processors.CallsiteParameterAdder(
                [CallsiteParameter.FILENAME,
                CallsiteParameter.FUNC_NAME,
                CallsiteParameter.LINENO],
            ),
            # ...
        ],
Run Code Online (Sandbox Code Playgroud)

所以,我想没有必要为此编写自定义处理器。但在官方文档中很难找到。


Bor*_*jaX 5

我有类似的需求,最终创建了一个自定义处理器

我接过来一看,以什么structlog,当它被告知要输出模块和行号“假装”对格式与兼容模式logging库(意思是:当它使用普通的stdlib.LoggerFactory),我找到了灵感在这。关键是下面几个字……

通过使用 structlog 的 structlog.stdlib.LoggerFactory,还可以确保在日志格式中正确扩展函数名称和行号等变量。

...从这个文档页面

代码似乎一直在寻找执行帧,直到它在与日志无关的模块中找到一个。

我有structlog一个模块内部的所有设置,my_libs.util.logger所以我想获得不在该模块内部的第一帧。为了做到这一点,我告诉它添加与my_libs.util.logger这些排除项相关的日志记录。这就是additional_ignores下面代码中的作用。

在示例'my_libs.util.logger'中,为了清楚起见,我在排除列表中硬编码了模块的名称 ( ),但如果您有类似的设置,您可能最好使用它__name__。这样做是忽略由于日志机制到位而存在的执行帧。您可以将其视为一种忽略在实际记录消息的过程中可能发生的调用的方法。或者,否则说,所发生的呼叫logging.info("Foo"),在实际的模块/线路发生,你想要输出。

一旦找到正确的框架,提取任何类型的信息(模块名称、函数名称、行号...)都非常容易,尤其是使用检查模块。我选择输出模块名称和行号,但可以添加更多字段。

# file my_libs/util/logger.py
import inspect
from structlog._frames import _find_first_app_frame_and_name

def show_module_info_processor(logger, _, event_dict):
    # If by any chance the record already contains a `modline` key,
    # (very rare) move that into a 'modline_original' key
    if 'modline' in event_dict:
        event_dict['modline_original'] = event_dict['modline']
    f, name = _find_first_app_frame_and_name(additional_ignores=[
        "logging",
        'my_libs.util.logger',  # could just be __name__
    ])
    if not f:
        return event_dict
    frameinfo = inspect.getframeinfo(f)
    if not frameinfo:
        return event_dict
    module = inspect.getmodule(f)
    if not module:
        return event_dict

    if frameinfo and module:
        # The `if` above is probably redundant, since we already
        # checked for frameinfo and module but... eh... paranoia.
        event_dict['modline'] = '{}:{}'.format(
            module.__name__,
            frameinfo.lineno,
        )
    return event_dict

def setup_structlog(env=None):
    # . . . 
    ch.setFormatter(logging.Formatter('%(message)s'))

    logging.getLogger().handlers = [ch]

    processors = [
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        # . . . more . . . 
        show_module_info_processor,  # THIS!!!
        structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
        structlog.processors.format_exc_info,
        structlog.processors.StackInfoRenderer(),
        # . . . more . . . 
    ]

    # . . . more . . . 
    structlog.configure_once(
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=structlog.stdlib.BoundLogger,
        context_class=structlog.threadlocal.wrap_dict(dict),
        processors=processors,
    )
Run Code Online (Sandbox Code Playgroud)

这会产生如下输出:

server_1                           
| INFO  [my_libs.hdfs] 2019-07-01 01:01:01 [info     ] Initialized HDFS               
[my_libs.hdfs] modline=my_libs.hdfs:31
Run Code Online (Sandbox Code Playgroud)


小智 3

看看这个关于如何获取行号的更普遍问题的答案。 /sf/answers/213938931/ 这不能使用 log.bind(...) 绑定到记录器,因为每次记录时都必须对其进行评估。因此,您应该添加像这样的键值对

logger.log(..., lineno=inspect.getframeinfo(inspect.currentframe()).lineno)
Run Code Online (Sandbox Code Playgroud)

每一次。不过,也许可以将其包装在一个函数中,如下所示: https: //stackoverflow.com/a/20372465/5909155 不要忘记

import inspect
Run Code Online (Sandbox Code Playgroud)