【发布时间】:2012-03-25 01:46:56
【问题描述】:
Python 的日志记录模块允许模块或类定义自己的记录器。不同的记录器可以有不同的处理程序。他们中的一些人可能会选择登录到文件,而有些人可能会选择登录到例如标准输出。
现在我的应用程序使用了其中的几个模块,每个模块都有自己的记录器,这些记录器具有各种处理程序。我可以统一日志记录行为,以便所有日志都转到我指定的日志文件吗?换句话说,有没有办法从一个地方一次 .config() 所有记录器的处理程序?
【问题讨论】:
Python 的日志记录模块允许模块或类定义自己的记录器。不同的记录器可以有不同的处理程序。他们中的一些人可能会选择登录到文件,而有些人可能会选择登录到例如标准输出。
现在我的应用程序使用了其中的几个模块,每个模块都有自己的记录器,这些记录器具有各种处理程序。我可以统一日志记录行为,以便所有日志都转到我指定的日志文件吗?换句话说,有没有办法从一个地方一次 .config() 所有记录器的处理程序?
【问题讨论】:
您可能应该查看Python Logging HOWTO 以了解其工作原理。
简而言之,模块通常所做的只是获取G_LOG = logging.getLogger('package.name') 形式的记录器并将消息发送到记录器:G_LOG.info('some message'), G_LOG.exception('something bad happened')。模块通常不应该配置任何东西。
使用模块的应用程序可以打开日志记录并根据记录器名称配置处理程序:
package 开头的记录器的消息,或者package.name 等开头的记录器的消息最简单的方法是在应用程序开头的某处通过logging.basicConfig 配置日志记录:
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)-8s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename=log_file, filemode='a')
这样您就可以将所有模块中的所有日志消息写入log_file。
如果您需要更详细的日志记录策略(将来自不同记录器的日志放入不同的文件,或者将堆栈跟踪发送到单独的文件),最好定义一个日志记录配置文件并使用logging.config.dictConfig 或logging.config.fileConfig 配置日志记录.
附:我通常创建两个记录器作为模块变量:
G_LOG = logging.getLogger(__name__)
ST_LOG = logging.getLogger('stacktrace.' + __name__)
到G_LOG 我只发送一条消息。对于ST_LOG,我使用隐含exc_info=True 的ST_LOG.exception 复制重要消息,并写入当前异常的堆栈跟踪。
在应用程序开始时,我加载了一个配置,该配置配置了两个记录器(和两个文件处理程序):一个接收以stacktrace 开头并具有propagate=0 的消息(即堆栈跟踪消息在顶部)和处理其余消息的根记录器。我不会把我完整的日志配置文件放在这里,因为了解它是如何工作的是一项有用的家庭作业。
【讨论】:
子记录器将消息传播到与其祖先记录器关联的处理程序。因此,没有必要为应用程序使用的所有记录器定义和配置处理程序。为顶级记录器配置处理程序并根据需要创建子记录器就足够了。 (但是,您可以通过将记录器的传播属性设置为 False 来关闭传播。)
当子记录器创建日志条目时,将使用您添加到根记录器的任何处理程序。
import logging
root_handler = ...
root_logger = logging.getLogger()
root_logger.addHandler(root_handler) # Will receive all log entries
# Meanwhile in a module...
import logging
logger = logging.getLogger(__name__)
logger.error(...) # Will go to root_handler
【讨论】:
日志模块提供分层记录器,这意味着如果根记录器(您使用logging.getLogger() 获得的记录器)以某种方式格式化所有具有其他名称的记录器(logging.getLogger("other_logger"))将被格式化(除非如果您将propagate 设置为False)
上述链接中解释的大型项目的最佳做法是在包的开头定义一个记录器配置(即在__main__.py),然后调用
logging.getLogger(__name__)
应用:
src
├── animals
│ ├── __init__.py
│ ├── dog.py
| |── cat.py
| |── fish.py
└── __main__.py
main.py内部:
import logging
from logging.config import dictConfig
LOG_CONFIG = {
'version': 1,
'handlers': {
'console': {
'level': 'DEBUG',
'formatter': 'std',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stdout'
},
'detailed_console': {
'level': 'DEBUG',
'formatter': 'error',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stdout'
},
'std_fh': {
'level': 'INFO',
'formatter': 'std',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/std.log',
'mode': 'a',
'maxBytes': 1048576,
'backupCount': 10
},
'detailed_fh': {
'level': 'WARNING',
'formatter': 'error',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/errors.log',
'mode': 'a',
'maxBytes': 1048576,
'backupCount': 10
}
},
'loggers': {
'': { # root logger
'level':'NOTSET',
'handlers': ['std_fh', 'console'],
},
'D.src': {
'propagate': False,
'handlers': ['detailed_fh','detailed_console'],
},
'src': {
'propagate': False,
'handlers': ['std_fh','console'],
}
},
'formatters': {
'std': {
'format': '[%(levelname)s - %(asctime)s - %(name)s::] %(message)s'
},
'error': {
'format': '[%(levelname)s - %(asctime)s - %(name)s - %(process)d::module :%(module)s|Line: %(lineno)s] messages:[ %(message)s ]'
},
}
}
logging.config.dictConfig(LOG_CONFIG)
rootL = logging.getLogger() # this is a root logger
srcL = logging.getLogger(__name__) # this is an `src` logger
detL = logging.getLogger("D.") # this is 'detailed'
def main():
rootL.debug("hello from rootL")
srcL.debug("hello from srcL")
detL.debug("hello from detL")
现在,让我们看看它们是如何从 main.py
打印的>>> python -m src
>>> [DEBUG - 2022-01-28 04:09:14,137 - root::] hello from rootL
>>> [DEBUG - 2022-01-28 04:09:14,137 - __main__::] hello from srcL
>>> [DEBUG - 2022-01-28 04:20:45,153 - D.src - 36742::module :__main__|Line: 83] messages:[ hello from detL ]
现在在 cat.py 中,您可以执行相同的操作,但无需再次配置。
import logging
rootL = logging.getLogger() # this is a root logger
srcL = logging.getLogger(__name__) # this is an `src` logger
detL = logging.getLogger("D."+__name__) # this is 'detailed'
rootL.debug("hello from rootL")
srcL.debug("hello from srcL")
detL.debug("hello from detL")
我建议使用dict 配置而不是文件配置来采取安全措施 (search here for eval())。
【讨论】: