Python 如何在 logging 中自动添加当前函数名和行号

Python logging可通过%(funcName)s和%(lineno)d自动添加函数名与行号,需在Formatter中配置格式字符串,封装日志函数时应设stacklevel=2以准确定位调用位置。

在 Python 的 logging 模块中,可以通过配置日志格式自动添加当前函数名和行号,无需每次手动传入。关键在于使用内置的格式化占位符 %(funcName)s%(lineno)d,并确保日志记录时能正确捕获调用位置(默认即可,无需额外设置)。

配置 logging 格式字符串

在初始化 logger 时,通过 Formatter 指定包含函数名和行号的格式:

  • %(funcName)s:自动提取日志调用所在函数的名称(注意:不是被调用函数,而是写 logger.info(...) 那一行所在的函数)
  • %(lineno)d:自动提取该日志语句所在的源代码行号
  • 其他常用占位符如 %(filename)s%(levelname)s%(asctime)s 可一并加入,提升可读性

示例:

import logging

formatter = logging.Formatter( '[%(asctime)s] %(levelname)-8s %(filename)s:%(lineno)d %(funcName)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' )

handler = logging.StreamHandler() handler.setFormatter(formatter)

logger = logging.getLogger(name) logger.setLevel(logging.DEBUG) logger.add

Handler(handler)

def example(): logger.debug("这条日志会显示函数名和行号")

example()

输出类似:
[2025-05-20 10:30:45] DEBUG test.py:12 example - 这条日志会显示函数名和行号

确保 logger 记录的是调用点而非内部位置

logging 默认使用 stacklevel=1,即向上查 1 层栈帧,正好定位到你写 logger.xxx() 的那行。一般无需改动。但如果你封装了日志函数(比如写了个 log_debug(msg)),则需显式设 stacklevel=2,否则函数名会显示为 log_debug,行号也会是封装函数内的行号。

  • 封装日志时,在调用 logger.xxx() 时加参数:extra={...}, stacklevel=2
  • 或直接用 logger.log(level, msg, stacklevel=2)

推荐的最小实用配置(支持文件+控制台)

兼顾开发调试与简单部署:

import logging

def setup_logger(name=name, level=logging.DEBUG): logger = logging.getLogger(name) logger.setLevel(level)

# 避免重复添加 handler
if not logger.handlers:
    formatter = logging.Formatter(
        '%(asctime)s | %(levelname)-8s | %(funcName)s:%(lineno)d | %(message)s',
        datefmt='%H:%M:%S'
    )

    ch = logging.StreamHandler()
    ch.setFormatter(formatter)
    logger.addHandler(ch)

    # 可选:添加文件 handler
    # fh = logging.FileHandler('app.log')
    # fh.setFormatter(formatter)
    # logger.addHandler(fh)

return logger

logger = setup_logger()

注意事项

  • 函数名取自调用日志语句的函数,不是被调用函数;若在 lambda 或顶层模块中调用,funcName 会显示为
  • lineno 是日志语句本身所在行,不是函数定义行
  • 多线程下信息仍准确,logging 内部基于当前线程栈帧获取
  • 性能影响极小,格式化只在实际输出时发生(除非设置了 logging.basicConfig(level=logging.DEBUG) 且日志未被过滤)