如何在Python中记录源文件名和行号

220

是否有可能装饰/扩展Python标准日志系统,以便在调用日志方法时,它也记录调用它的文件和行号,或者可能是调用它的方法?

8个回答

387

可以查看logging文档中的格式化程序。具体来说,使用linenopathname变量。

%(pathname)s 表示发出日志调用的源文件的完整路径名(如果有)。

%(filename)s 表示路径名的文件名部分。

%(module)s 表示模块(即文件名的名称部分)。

%(funcName)s 表示包含日志调用的函数名称。

%(lineno)d 表示发出日志调用的源行号(如果有)。

看起来应该是这样的:

formatter = logging.Formatter('[%(asctime)s] p%(process)s {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s','%m-%d %H:%M:%S')

5
是的,需要考虑变量中的大小写混乱问题。 - Tom Pohl
3
通常也称为“非常糟糕的驼峰命名法”。 - Jon Spencer
2
我正在使用Logger.py文件在我的文件之间共享我的记录器,当我使用这个语法时,我得到的是Logger的文件名和行号,而不是调用记录器方法所使用的文件和行。有什么解决办法吗? - user2396640
你为什么要在代码中使用 {}...)d} - Charlie Parker
1
p%(process)s是什么意思?特别是p和s。 - Charlie Parker
显示剩余2条评论

169

除了Seb的非常有用的回答之外,这里还有一个方便的代码片段,演示了使用合理格式的记录器的用法:

#!/usr/bin/env python
import logging

logging.basicConfig(format='%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s',
    datefmt='%Y-%m-%d:%H:%M:%S',
    level=logging.DEBUG)

logger = logging.getLogger(__name__)
logger.debug("This is a debug log")
logger.info("This is an info log")
logger.critical("This is critical")
logger.error("An error occurred")

生成以下输出:

2017-06-06:17:07:02,158 DEBUG    [log.py:11] This is a debug log
2017-06-06:17:07:02,158 INFO     [log.py:12] This is an info log
2017-06-06:17:07:02,158 CRITICAL [log.py:13] This is critical
2017-06-06:17:07:02,158 ERROR    [log.py:14] An error occurred

7
使用以下内容获取更多细节: formatter = logging.Formatter( '%(asctime)s, %(levelname)-8s [%(filename)s:%(module)s:%(funcName)s:%(lineno)d] %(message)s') - Girish Gupta
有没有一种方法可以在代码顶部的一个地方更改日志消息是否被打印?我想要两种模式,一种是大量打印以查看程序的确切操作;另一种是当程序足够稳定时不显示任何输出。 - Marie. P.
6
@Marie.P. 不要在评论中问不同的问题。答案是日志级别。 - bugmenot123

13
import logging

# your imports above ...


logging.basicConfig(
    format='%(asctime)s,%(msecs)d %(levelname)-8s [%(pathname)s:%(lineno)d in ' \
           'function %(funcName)s] %(message)s',
    datefmt='%Y-%m-%d:%H:%M:%S',
    level=logging.DEBUG
)

logger = logging.getLogger(__name__)

# your classes and methods below ...
# A very naive sample of usage:
try:
    logger.info('Sample of info log')
    # your code here
except Exception as e:
    logger.error(e)
不同于其他答案,这个方法会记录出现错误的完整文件路径和函数名。如果你的项目有多个模块,并且这些模块中分布了几个同名文件,那么这个方法就非常有用。
示例输出:
2022-12-02:10:00:00,000 INFO     [<stdin>:2 in function <module>] Sample of info log
2022-12-02:10:00:00,000 INFO     [<full path>/logging_test_file.py:15 in function <module>] Sample of info log

10
为了在控制台输出调试日志,可以按照以下方式进行构建:
import logging
import sys

root = logging.getLogger()
root.setLevel(logging.DEBUG)

ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.DEBUG)
FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s"
formatter = logging.Formatter(FORMAT)
ch.setFormatter(formatter)
root.addHandler(ch)

logging.debug("I am sent to standard out.")

将以上内容放入一个名为debug_logging_example.py的文件中,会产生以下输出:
[debug_logging_example.py:14 -             <module>() ] I am sent to standard out.

如果您想关闭日志记录,则可以将root.setLevel(logging.DEBUG)注释掉。

对于单个文件(例如班级作业),与使用print()语句相比,我发现这是一种更好的方法。它允许您在提交之前在一个地方关闭调试输出。


7

对于使用PyCharm或Eclipse PyDev的开发人员,以下内容将在控制台日志输出中生成指向日志语句源代码的链接:

import logging, sys, os
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, format='%(message)s | \'%(name)s:%(lineno)s\'')
log = logging.getLogger(os.path.basename(__file__))


log.debug("hello logging linked to source")

请查看 Pydev源文件超链接在Eclipse控制台中的应用,获取更详细的讨论和历史记录。


1
如果使用GetLogger(name)选项设置记录器,其中name是您指定的名称,则还可以使用%(name)s格式化记录器。您可以使用GetLogger函数在每个文件中指定不同的名称,当生成日志时,您将通过设置的名称知道来自哪个文件。 示例:
import logging

logging.getLogger("main")
logging.basicConfig(#filename=log_fpath, 
                    level=log_level,
                    format='[%(asctime)s] src:%(name)s %(levelname)s:%(message)s',
                    handlers=[logging.FileHandler(log_fpath)])

0
创建一个带有log_format的pytest.ini文件。

pytest.ini

log_format = %(asctime)s %(levelname) %(message)s (%(pathname)s:%(lineno)s)
log_cli_level = DEBUG

0
原始问题提到了“装饰/扩展”。不确定是我想太多还是其他人走错了方向。如果我们要装饰Python默认的日志记录功能,并且希望堆栈信息正常工作,例如在装饰之外显示所需的堆栈的“文件名”和“行号”,那么在另一篇帖子中有一个来自@Tony S Yu的答案:https://dev59.com/7VwY5IYBdhLWcg3wl4ob#55998744 关键是使用stacklevel属性,我将用一段代码进行演示:
logging.basicConfig(
    format=('%(asctime)s,%(msecs)03d %(levelname)-8s '
            '[%(filename)s:%(lineno)d] %(message)s'),
    level=logging.DEBUG,
    datefmt='%H:%M:%S')

def my_log(*args, **kwargs):
    indent = kwargs.pop('indent', 0)
    msg = ' ' * indent + ' '.join([str(a) for a in args])
    print(f'[MYLOG] {msg}')
    return logging.info(msg, stacklevel=2, **kwargs)

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