在多个模块中使用日志记录
- 2025-02-12 10:03:00
- admin 原创
- 50
问题描述:
我有一个小型的 Python 项目,其结构如下 -
Project
-- pkg01
-- test01.py
-- pkg02
-- test02.py
-- logging.conf
我计划使用默认日志模块将消息打印到 stdout 和日志文件。要使用日志模块,需要进行一些初始化 -
import logging.config
logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')
logger.info('testing')
目前,我在开始记录消息之前在每个模块中执行此初始化。是否可以在一个地方只执行一次此初始化,以便在整个项目中记录时重复使用相同的设置?
解决方案 1:
最佳做法是在每个模块中定义一个如下的记录器:
import logging
logger = logging.getLogger(__name__)
靠近模块顶部,然后在模块中的其他代码中执行例如
logger.debug('My message with %s', 'variable data')
如果你需要细分模块内的日志记录活动,请使用例如
loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')
并根据需要记录到loggerA
和。loggerB
在您的主程序或程序中,执行以下操作:
def main():
"your program code"
if __name__ == '__main__':
import logging.config
logging.config.fileConfig('/path/to/logging.conf')
main()
或者
def main():
import logging.config
logging.config.fileConfig('/path/to/logging.conf')
# your program code
if __name__ == '__main__':
main()
请参阅此处了解多个模块的日志记录,并参阅此处了解将被其他代码用作库模块的代码的日志记录配置。
更新:调用 时fileConfig()
,您可能需要指定disable_existing_loggers=False
是否使用 Python 2.6 或更高版本(有关更多信息,请参阅文档)。默认值是True
为了向后兼容,这会导致所有现有记录器都被禁用,fileConfig()
除非它们或其祖先在配置中明确命名。将值设置为 后False
,现有记录器将保持不变。如果使用 Python 2.7/Python 3.2 或更高版本,您可能希望考虑dictConfig()
比 更好的 API,fileConfig()
因为它可以更好地控制配置。
解决方案 2:
实际上,每个记录器都是父包记录器的子项(即package.subpackage.module
从继承配置package.subpackage)
,因此您需要做的只是配置根记录器。这可以通过logging.config.fileConfig
(您自己的记录器配置)或logging.basicConfig
(设置根记录器)来实现。在您的入口模块中设置日志记录(__main__.py
或您想要运行的任何内容,例如main_script.py
。__init__.py
也可以)
使用基本配置:
# package/__main__.py
import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
使用文件配置:
# package/__main__.py
import logging
import logging.config
logging.config.fileConfig('logging.conf')
然后使用以下命令创建每个记录器:
# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)
log.info("Hello logging!")
有关更多信息,请参阅高级日志教程。
解决方案 3:
对我来说,在多个模块中使用一个日志库实例的一个简单方法是以下解决方案:
基础日志记录器.py
import logging
logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)
其他文件
from base_logger import logger
if __name__ == '__main__':
logger.info("This is an info message")
解决方案 4:
我想添加我的解决方案(它基于日志记录手册和此线程中的其他文章和建议。但是我花了很长时间才弄清楚为什么它没有立即按我预期的方式工作。所以我创建了一个小测试项目来了解日志记录的工作原理。
既然我已经明白了,我想分享我的解决方案,也许它可以对某些人有所帮助。
我知道我的一些代码可能不是最佳实践,但我仍在学习。我保留了这些print()
函数,因为我使用它们,但日志记录没有按预期工作。这些函数已从我的其他应用程序中删除。此外,我欢迎对代码或结构的任何部分提出任何反馈。
my_log_test 项目结构(从我从事的另一个项目克隆/简化)
my_log_test
├── __init__.py
├── __main__.py
├── daemon.py
├── common
│ ├── my_logger.py
├── pkg1
│ ├── __init__.py
│ └── mod1.py
└── pkg2
├── __init__.py
└── mod2.py
要求
在我使用的组合中,有一些不同之处或我没有看到明确提到的地方:
主要模块
daemon.py
是__main__.py
我希望能够在开发/测试过程中单独调用模块
mod1.py
和mod2.py
此时我不想使用
basicConfig()
或FileConfig()
但保留它就像在日志记录手册中一样
所以基本上,这意味着,我需要在(总是)和模块中初始化根记录器(仅在直接调用它们时)。daemon.py
`mod1.py`mod2.py
为了使几个模块中的初始化更容易,我创建了my_logger.py
可以执行食谱中描述的操作的模块。
我的错误
此前,我在该模块中的错误是使用(模块记录器)来初始化记录器,logger = logging.getLogger(__name__)
而不是使用logger = logging.getLogger()
(获取根记录器)。
第一个问题是,当从daemon.py
设置为 的记录器命名空间调用时。因此,具有“不匹配”命名空间my_log_test.common.my_logger
的模块记录器无法附加到其他记录器,并且我看不到来自 mod1 的日志输出。mod1.py
`my_log_test.pkg1.mod1`
第二个“问题”是,我的主程序在daemon.py
而不是 在__main__.py
。但对我来说这毕竟不是一个真正的问题,但它增加了命名空间的混乱。
工作解决方案
这是来自 cookbook 但在单独的模块中。我还添加了一个logger_cleanup
可以从守护进程调用的函数,用于删除超过 x 天的日志。
## my_logger.py
from datetime import datetime
import time
import os
## Init logging start
import logging
import logging.handlers
def logger_init():
print("print in my_logger.logger_init()")
print("print my_logger.py __name__: " +__name__)
path = "log/"
filename = "my_log_test.log"
## get logger
#logger = logging.getLogger(__name__) ## this was my mistake, to init a module logger here
logger = logging.getLogger() ## root logger
logger.setLevel(logging.INFO)
# File handler
logfilename = datetime.now().strftime("%Y%m%d_%H%M%S") + f"_{filename}"
file = logging.handlers.TimedRotatingFileHandler(f"{path}{logfilename}", when="midnight", interval=1)
#fileformat = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
fileformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s")
file.setLevel(logging.INFO)
file.setFormatter(fileformat)
# Stream handler
stream = logging.StreamHandler()
#streamformat = logging.Formatter("%(asctime)s [%(levelname)s:%(module)s] %(message)s")
streamformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s")
stream.setLevel(logging.INFO)
stream.setFormatter(streamformat)
# Adding all handlers to the logs
logger.addHandler(file)
logger.addHandler(stream)
def logger_cleanup(path, days_to_keep):
lclogger = logging.getLogger(__name__)
logpath = f"{path}"
now = time.time()
for filename in os.listdir(logpath):
filestamp = os.stat(os.path.join(logpath, filename)).st_mtime
filecompare = now - days_to_keep * 86400
if filestamp < filecompare:
lclogger.info("Delete old log " + filename)
try:
os.remove(os.path.join(logpath, filename))
except Exception as e:
lclogger.exception(e)
continue
运行 deamon.py (通过__main__.py
)使用python3 -m my_log_test
## __main__.py
from my_log_test import daemon
if __name__ == '__main__':
print("print in __main__.py")
daemon.run()
要运行 deamon.py (直接),请使用python3 -m my_log_test.daemon
## daemon.py
from datetime import datetime
import time
import logging
import my_log_test.pkg1.mod1 as mod1
import my_log_test.pkg2.mod2 as mod2
## init ROOT logger from my_logger.logger_init()
from my_log_test.common.my_logger import logger_init
logger_init() ## init root logger
logger = logging.getLogger(__name__) ## module logger
def run():
print("print in daemon.run()")
print("print daemon.py __name__: " +__name__)
logger.info("Start daemon")
loop_count = 1
while True:
logger.info(f"loop_count: {loop_count}")
logger.info("do stuff from pkg1")
mod1.do1()
logger.info("finished stuff from pkg1")
logger.info("do stuff from pkg2")
mod2.do2()
logger.info("finished stuff from pkg2")
logger.info("Waiting a bit...")
time.sleep(30)
if __name__ == '__main__':
try:
print("print in daemon.py if __name__ == '__main__'")
logger.info("running daemon.py as main")
run()
except KeyboardInterrupt as e:
logger.info("Program aborted by user")
except Exception as e:
logger.info(e)
要运行 mod1.py(直接),请使用python3 -m my_log_test.pkg1.mod1
## mod1.py
import logging
# mod1_logger = logging.getLogger(__name__)
mod1_logger = logging.getLogger("my_log_test.daemon.pkg1.mod1") ## for testing, namespace set manually
def do1():
print("print in mod1.do1()")
print("print mod1.py __name__: " +__name__)
mod1_logger.info("Doing someting in pkg1.do1()")
if __name__ == '__main__':
## Also enable this pkg to be run directly while in development with
## python3 -m my_log_test.pkg1.mod1
## init root logger
from my_log_test.common.my_logger import logger_init
logger_init() ## init root logger
print("print in mod1.py if __name__ == '__main__'")
mod1_logger.info("Running mod1.py as main")
do1()
要运行 mod2.py(直接),请使用python3 -m my_log_test.pkg2.mod2
## mod2.py
import logging
logger = logging.getLogger(__name__)
def do2():
print("print in pkg2.do2()")
print("print mod2.py __name__: " +__name__) # setting namespace through __name__
logger.info("Doing someting in pkg2.do2()")
if __name__ == '__main__':
## Also enable this pkg to be run directly while in development with
## python3 -m my_log_test.pkg2.mod2
## init root logger
from my_log_test.common.my_logger import logger_init
logger_init() ## init root logger
print("print in mod2.py if __name__ == '__main__'")
logger.info("Running mod2.py as main")
do2()
如果有帮助的话我会很高兴。收到反馈也很高兴!
解决方案 5:
我总是按照下面的方式来做。
使用单个 python 文件将我的日志配置为名为“ log_conf.py
”的单例模式
#-*-coding:utf-8-*-
import logging.config
def singleton(cls):
instances = {}
def get_instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return get_instance()
@singleton
class Logger():
def __init__(self):
logging.config.fileConfig('logging.conf')
self.logr = logging.getLogger('root')
在另一个模块中,只需导入配置。
from log_conf import Logger
Logger.logr.info("Hello World")
这是一个单例模式的记录方式,简单而高效。
解决方案 6:
提出另一种解决方案。
在我的模块的init.py中我有类似这样的内容:
# mymodule/__init__.py
import logging
def get_module_logger(mod_name):
logger = logging.getLogger(mod_name)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
return logger
然后在每个模块中我都需要一个记录器,我会这样做:
# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)
当日志丢失时,您可以通过日志所来自的模块来区分其来源。
解决方案 7:
这些答案中有几个建议在模块顶部你做
import logging
logger = logging.getLogger(__name__)
据我所知,这是非常糟糕的做法。原因是文件配置默认会禁用所有现有记录器。例如
#my_module
import logging
logger = logging.getLogger(__name__)
def foo():
logger.info('Hi, foo')
class Bar(object):
def bar(self):
logger.info('Hi, bar')
在你的主模块中:
#main
import logging
# load my module - this now configures the logger
import my_module
# This will now disable the logger in my module by default, [see the docs][1]
logging.config.fileConfig('logging.ini')
my_module.foo()
bar = my_module.Bar()
bar.bar()
现在,logging.ini 中指定的日志将为空,因为现有的记录器已被 fileconfig 调用禁用。
虽然确实可以解决这个问题(disable_existing_Loggers=False),但实际上,您的库的许多客户端都不知道此行为,也不会收到您的日志。通过始终在本地调用logging.getLogger,让您的客户端轻松完成此操作。提示:我从Victor Lin 的网站了解到此行为。
因此,好的做法是始终在本地调用logging.getLogger。例如
#my_module
import logging
logger = logging.getLogger(__name__)
def foo():
logging.getLogger(__name__).info('Hi, foo')
class Bar(object):
def bar(self):
logging.getLogger(__name__).info('Hi, bar')
此外,如果您在主程序中使用 fileconfig,请设置 disable_existing_loggers=False,以防您的库设计人员使用模块级记录器实例。
解决方案 8:
你也可以想出类似的东西!
def get_logger(name=None):
default = "__app__"
formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
if name:
logger = logging.getLogger(name)
else:
logger = logging.getLogger(default)
fh = logging.FileHandler(log_map[name])
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.setLevel(logging.DEBUG)
return logger
现在,如果上述内容在单独的模块中定义并导入到需要日志记录的其他模块中,则可以在同一个模块和整个项目中使用多个记录器。
a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")
解决方案 9:
@Yarkee 的解决方案似乎更好。我想补充一些内容 -
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances.keys():
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class LoggerManager(object):
__metaclass__ = Singleton
_loggers = {}
def __init__(self, *args, **kwargs):
pass
@staticmethod
def getLogger(name=None):
if not name:
logging.basicConfig()
return logging.getLogger()
elif name not in LoggerManager._loggers.keys():
logging.basicConfig()
LoggerManager._loggers[name] = logging.getLogger(str(name))
return LoggerManager._loggers[name]
log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)
因此 LoggerManager 可以插入到整个应用程序中。希望它有意义且有价值。
解决方案 10:
有几种答案。我最终找到了一个类似但不同的解决方案,对我来说很有意义,也许对你来说也一样有意义。我的主要目标是能够按级别将日志传递给处理程序(调试级别日志传递到控制台,警告及以上级别传递到文件):
from flask import Flask
import logging
from logging.handlers import RotatingFileHandler
app = Flask(__name__)
# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)
rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)
app.logger.addHandler(rotating_file_handler)
创建了一个名为 logger.py 的实用文件:
import logging
def get_logger(name):
return logging.getLogger("flask.app." + name)
flask.app 是 flask 中的硬编码值。应用程序记录器始终以 flask.app 作为模块名称开头。
现在,在每个模块中,我都可以按照以下模式使用它:
from logger import get_logger
logger = get_logger(__name__)
logger.info("new log")
这将以最少的努力为“app.flask.MODULE_NAME”创建一个新的日志。
解决方案 11:
最佳实践是单独创建一个模块,该模块只有一个方法,其任务是为调用方法提供一个记录器处理程序。将此文件保存为 m_logger.py
import logger, logging
def getlogger():
# logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
#ch = logging.StreamHandler()
ch = logging.FileHandler(r'log.txt')
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
return logger
现在,每当需要记录器处理程序时,就调用 getlogger() 方法。
from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')
解决方案 12:
我是 Python 新手,所以我不知道这是否可取,但它对于不重写样板文件非常有用。
你的项目必须有一个init .py,以便可以将其作为模块加载
# Put this in your module's __init__.py
import logging.config
import sys
# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always
# use "root" but I can't get that to work
logging.config.dictConfig({
"version": 1,
"formatters": {
"default": {
"format": "%(asctime)s %(levelname)s %(name)s %(message)s"
},
},
"handlers": {
"console": {
"level": 'DEBUG',
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout"
}
},
"loggers": {
"": {
"level": "DEBUG",
"handlers": ["console"]
}
}
})
def logger():
# Get the name from the caller of this function
return logging.getLogger(sys._getframe(1).f_globals['__name__'])
sys._getframe(1)
建议来自这里
然后在任何其他文件中使用您的记录器:
from [your module name here] import logger
logger().debug("FOOOOOOOOO!!!")
注意事项:
您必须将文件作为模块运行,否则
import [your module]
将无法工作:
* `python -m [your module name].[your filename without .py]`
程序入口点的记录器名称将是
__main__
,但任何使用的解决方案__name__
都会有该问题。