Python中使用logging调用Logger.info方法的处理过程

本次分析一下Logger.info的流程

1. Logger.info源码:

    def info(self, msg, *args, **kwargs):
        """
        Log 'msg % args' with severity 'INFO'.

        To pass exception information, use the keyword argument exc_info with
        a true value, e.g.

        logger.info("Houston, we have a %s", "interesting problem", exc_info=1)
        """
        if self.isEnabledFor(INFO):
            self._log(INFO, msg, args, **kwargs)

注释中反应了可以通过 msg和不定参数args来进行日志的格式化。
真实的调用为:_log方法:

2. Logger._log方法:

    def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
        """
        Low-level logging routine which creates a LogRecord and then calls
        all the handlers of this logger to handle the record.
        """
        sinfo = None
        if _srcfile:
            #IronPython doesn't track Python frames, so findCaller raises an
            #exception on some versions of IronPython. We trap it here so that
            #IronPython can use logging.
            try:
                fn, lno, func, sinfo = self.findCaller(stack_info)
            except ValueError: # pragma: no cover
                fn, lno, func = "(unknown file)", 0, "(unknown function)"
        else: # pragma: no cover
            fn, lno, func = "(unknown file)", 0, "(unknown function)"
        if exc_info:
            if isinstance(exc_info, BaseException):
                exc_info = (type(exc_info), exc_info, exc_info.__traceback__)
            elif not isinstance(exc_info, tuple):
                exc_info = sys.exc_info()
        record = self.makeRecord(self.name, level, fn, lno, msg, args,
                                 exc_info, func, extra, sinfo)
        self.handle(record)

最后两行:
1. 生成日志记录:
record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra, sinfo)
2. 处理日志记录
self.handle(record)

2 生成日志记录:

    def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
                   func=None, extra=None, sinfo=None):
        """
        A factory method which can be overridden in subclasses to create
        specialized LogRecords.
        """
        rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func,
                             sinfo)
        if extra is not None:
            for key in extra:
                if (key in ["message", "asctime"]) or (key in rv.__dict__):
                    raise KeyError("Attempt to overwrite %r in LogRecord" % key)
                rv.__dict__[key] = extra[key]
        return rv

调用_logRecordFactory初始化一个日志记录实例,_logRecordFactory其实就是LogRecord类,初始化时,可能包含logger的name, level、调用的函数、行号、日志字符串、模板参数、堆栈信息等。
再看extra信息,extra到底有何用?现在从代码中可以看到,只是更新到生成的日志记录实例的__dict__中去.猜测:肯定会在生成最终的日志字符串的时候会用到。继续往下看。

3 处理日志记录self.handle(record):

Logger继承自Filterer,

    def handle(self, record):
        """
        Call the handlers for the specified record.

        This method is used for unpickled records received from a socket, as
        well as those created locally. Logger-level filtering is applied.
        """
        if (not self.disabled) and self.filter(record):
            self.callHandlers(record)
3.1 if语句中有一self.filter(record)的判断,看函数名,是来筛选是否要继续处理消息的,其核心源码如下:
    def filter(self, record):
        """
        Determine if a record is loggable by consulting all the filters.

        The default is to allow the record to be logged; any filter can veto
        this and the record is then dropped. Returns a zero value if a record
        is to be dropped, else non-zero.

        .. versionchanged:: 3.2

           Allow filters to be just callables.
        """
        rv = True
        for f in self.filters:
            if hasattr(f, 'filter'):
                result = f.filter(record)
            else:
                result = f(record) # assume callable - will raise if not
            if not result:
                rv = False
                break
        return rv

可以看到, 如果在handler中的filter中如果有返回为False或空,则会屏蔽对应的record,返回True或部位空的值,则会将record放行。那么我们就可以自定义自己的filter。

3.2 让Logger中所有的handles去处理record:
    def callHandlers(self, record):
        """
        Pass a record to all relevant handlers.

        Loop through all handlers for this logger and its parents in the
        logger hierarchy. If no handler was found, output a one-off error
        message to sys.stderr. Stop searching up the hierarchy whenever a
        logger with the "propagate" attribute set to zero is found - that
        will be the last logger whose handlers are called.
        """
        c = self
        found = 0
        while c:
            for hdlr in c.handlers:
                found = found + 1
                if record.levelno >= hdlr.level:
                    hdlr.handle(record)
            if not c.propagate:
                c = None    #break out
            else:
                c = c.parent
        if (found == 0):
            if lastResort:
                if record.levelno >= lastResort.level:
                    lastResort.handle(record)
            elif raiseExceptions and not self.manager.emittedNoHandlerWarning:
                sys.stderr.write("No handlers could be found for logger"
                                 " \"%s\"\n" % self.name)
                self.manager.emittedNoHandlerWarning = True

代码中会去循环调用当前logger的所有handlers去处理record,for循环部分,之后,如果当前的loggerpropagate的值为False或空,则不向logger父logger传递,即向上传递。

4. Handler 中的 handler(record) 部分:

    def handle(self, record):
        """
        Conditionally emit the specified logging record.

        Emission depends on filters which may have been added to the handler.
        Wrap the actual emission of the record with acquisition/release of
        the I/O thread lock. Returns whether the filter passed the record for
        emission.
        """
        rv = self.filter(record)
        if rv:
            self.acquire()
            try:
                self.emit(record)
            finally:
                self.release()
        return rv

可以看到, Handler在处理record时, 会去加锁,然后调用self.emit(record)方法去处理。

4.1 emit(record)
    def emit(self, record):
        """
        Do whatever it takes to actually log the specified logging record.

        This version is intended to be implemented by subclasses and so
        raises a NotImplementedError.
        """
        raise NotImplementedError('emit must be implemented '
                                  'by Handler subclasses')

看到需要由子类去实现,以StreamHandler为例子:

    def emit(self, record):
        """
        Emit a record.

        If a formatter is specified, it is used to format the record.
        The record is then written to the stream with a trailing newline.  If
        exception information is present, it is formatted using
        traceback.print_exception and appended to the stream.  If the stream
        has an 'encoding' attribute, it is used to determine how to do the
        output to the stream.
        """
        try:
            msg = self.format(record)
            stream = self.stream
            stream.write(msg)
            stream.write(self.terminator)
            self.flush()
        except Exception:
            self.handleError(record)
4.2 Handler.format(record):
    def format(self, record):
        """
        Format the specified record.

        If a formatter is set, use it. Otherwise, use the default formatter
        for the module.
        """
        if self.formatter:
            fmt = self.formatter
        else:
            fmt = _defaultFormatter
        return fmt.format(record)

如果handler有自定义的formatter就用自定义的,如果没有则用默认的Formatter的实例, 初始化元源码为:

    def __init__(self, fmt=None, datefmt=None, style='%'):
        """
        Initialize the formatter with specified format strings.

        Initialize the formatter either with the specified format string, or a
        default as described above. Allow for specialized date formatting with
        the optional datefmt argument (if omitted, you get the ISO8601 format).

        Use a style parameter of '%', '{' or '$' to specify that you want to
        use one of %-formatting, :meth:`str.format` (``{}``) formatting or
        :class:`string.Template` formatting in your format string.

        .. versionchanged:: 3.2
           Added the ``style`` parameter.
        """
        if style not in _STYLES:
            raise ValueError('Style must be one of: %s' % ','.join(
                             _STYLES.keys()))
        self._style = _STYLES[style][0](fmt)
        self._fmt = self._style._fmt
        self.datefmt = datefmt

有三个参数:
* fmt: 格式化模板
* datefmt: 时间格式化参数
* style: 日志格式化的样式。

style有三种:

_STYLES = {
    '%': (PercentStyle, BASIC_FORMAT),
    '{': (StrFormatStyle, '{levelname}:{name}:{message}'),
    '$': (StringTemplateStyle, '${levelname}:${name}:${message}'),

可以看出对应到:% 操作符的格式化, format方法的格式化以及Template的格式化。
Formatter的format方法源码为:

    def format(self, record):
        """
        Format the specified record as text.
        The record's attribute dictionary is used as the operand to a
        string formatting operation which yields the returned string.
        Before formatting the dictionary, a couple of preparatory steps
        are carried out. The message attribute of the record is computed
        using LogRecord.getMessage(). If the formatting string uses the
        time (as determined by a call to usesTime(), formatTime() is
        called to format the event time. If there is exception information,
        it is formatted using formatException() and appended to the message.
        """
        record.message = record.getMessage()
        if self.usesTime():
            record.asctime = self.formatTime(record, self.datefmt)
        s = self.formatMessage(record)
        if record.exc_info:
            # Cache the traceback text to avoid converting it multiple times
            # (it's constant anyway)
            if not record.exc_text:
                record.exc_text = self.formatException(record.exc_info)
        if record.exc_text:
            if s[-1:] != "\n":
                s = s + "\n"
            s = s + record.exc_text
        if record.stack_info:
            if s[-1:] != "\n":
                s = s + "\n"
            s = s + self.formatStack(record.stack_info)

看到会调用record.getMessage(),这里仅仅是获取我们需要的日志信息。
之后会调用s = self.formatMessage(record):

    def formatMessage(self, record):
        return self._style.format(record)

其实是调用了当前style的format方法,以%这一类型为例PercentStyle

class PercentStyle(object):

    default_format = '%(message)s'
    asctime_format = '%(asctime)s'
    asctime_search = '%(asctime)'

    def __init__(self, fmt):
        self._fmt = fmt or self.default_format

    def usesTime(self):
        return self._fmt.find(self.asctime_search) >= 0

    def format(self, record):
        return self._fmt % record.__dict__

从其中的format方法可以看出,是针对record__dict__属性中的所有参数进行格式化,这下,就清楚了之前的extra参数是干嘛用的了:可以在formatter中加入自己自定义的一些参数,如固定的用户信息等等。

之后,将最终的message flush到对应的Stream里面去就行了,就是整个流程:

官方文档中的处理流程

请大家多多指点。

<think>好的,我现在需要解决用户的问题:如何在Python使用logger.info打印一个列表。用户提到了引用中的logger.info用法,比如引用[2]中的格式化字符串输出参数数量。首先,我需要回忆Pythonlogging模块的基本用法,特别是logger.info使用方法。 用户可能已经知道基本的字符串格式化,比如使用%格式化或者str.format(),但问题是如何将这些方法应用到列表的日志记录中。列表作为可变对象,直接转换成字符串可能不够可读,尤其是在日志中需要清晰显示内容时。 首先,直接使用logger.info打印列表可能会有问题,因为直接传递列表对象的话,logger.info调用str(list),这样输出会是像[1, 2, 3]的形式,但可能用户希望更详细的格式,或者处理包含复杂元素的列表。例如,用户可能希望每个元素单独列出,或者进行更结构化的输出。 根据引用[2],用户使用了字符串格式化,例如'Parameter count: {}'.format(self.num_params)。这说明用户可能倾向于使用.format方法。那么对于列表,可以将列表转换为字符串后插入到日志消息中。例如: logger.info('列表内容: %s', my_list) 或者使用f-string(Python 3.6+): logger.info(f'列表内容: {my_list}') 不过需要注意,如果列表很大,直接转换为字符串可能会导致日志信息过长。另外,如果列表中包含需要特殊处理的对象(如非字符串类型),可能需要先进行转换,比如使用列表推导式将元素转为字符串。 另一种情况是用户希望美化输出,比如每个元素单独显示。这时候可能需要将列表的元素连接成字符串,例如用逗号分隔: logger.info('列表元素: %s', ', '.join(map(str, my_list))) 不过这里需要注意,如果列表中的元素不是字符串类型,需要先用map(str, ...)转换,否则join会报错。 此外,还可以考虑使用json.dumps来格式化列表,特别是当列表包含嵌套结构或字典时,这样输出会更易读: import json logger.info('列表内容: %s', json.dumps(my_list, indent=2)) 这会以JSON格式美化输出,适合复杂数据结构。 但用户的问题可能更基础,只需要一个简单的示例。因此,应该给出几种常见的方法,并说明其适用场景。同时,要确保示例代码正确,符合Python的语法和logging模块的使用规范。 需要检查是否有引用中的相关内容可以借鉴。引用[2]中的示例是使用.format方法,所以可能用户的项目中习惯用这种方式。例如: logger.info('列表内容: {}'.format(my_list)) 或者使用%格式化: logger.info('列表内容: %s', my_list) 后者更推荐,因为logging模块内部会进行字符串格式化,避免不必要的字符串操作,尤其是在日志级别设置较高时,可能不会输出该信息,从而节省资源。 另外,如果列表很大,可能需要考虑截断或分页显示,但用户的问题中没有提到这种情况,可能暂时不需要处理。 总结可能的解决方案: 1. 直接转换列表为字符串: logger.info('列表内容: %s', my_list) 2. 使用join方法连接元素: logger.info('元素:%s', ', '.join(map(str, my_list))) 3. 使用json美化输出: logger.info('结构化输出:%s', json.dumps(my_list, indent=2)) 需要指出不同方法的适用情况,比如简单列表、需要可读性更高的输出、复杂结构等。 同时,提醒用户注意元素类型转换的问题,例如非字符串元素需要先转换为字符串,否则在join时会出错。 可能还需要提到,如果使用f-string,在Python 3.6以上可用,但logging模块的%格式更高效,尤其是在日志未被记录时,避免不必要的字符串格式化操作。 最后,需要确保示例代码正确,并符合用户提供的引用中的风格,比如使用.format或者%格式。</think>在Python使用`logger.info`输出列表时,可以通过以下方法实现清晰的日志记录: ### 方法1:直接转换为字符串 使用格式化占位符`%s`将列表自动转换为字符串格式: ```python my_list = [1, 2, 3] logger.info('列表内容: %s', my_list) ``` 输出示例: ``` 列表内容: [1, 2, 3] ``` ### 方法2:拼接元素增强可读性 通过`join`方法将元素拼接为自定义格式的字符串: ```python logger.info('元素明细: %s', ', '.join(map(str, my_list))) ``` 输出示例: ``` 元素明细: 1, 2, 3 ``` ### 方法3:结构化输出(嵌套数据) 使用`json.dumps`美化复杂数据结构: ```python import json nested_list = [{"name": "Alice", "id": 101}, {"name": "Bob", "id": 102}] logger.info('结构化输出:\n%s', json.dumps(nested_list, indent=2)) ``` 输出示例: ``` 结构化输出: [ { "name": "Alice", "id": 101 }, { "name": "Bob", "id": 102 } ] ``` ### 对比说明 | 方法 | 适用场景 | 优势 | 注意事项 | |--------|-----------------------------|-----------------------------|-----------------------------| | 方法1 | 简单列表快速输出 | 代码简洁,无需额外处理 | 嵌套数据可读性差 | | 方法2 | 需明确显示元素 | 元素分离清晰 | 元素需为字符串或手动转换 | | 方法3 | 嵌套字典/多层级数据结构 | 结构清晰,便于调试 | 需导入`json`库,数据需可序列化 | ### 引用说明 当需要记录对象参数时,可参考类似`logger.info('Parameter count: {}'.format(self.num_params))`的格式化方式[^2]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值