我想添加我的解决方案(这是基于日志记录烹饪书和本线程中的其他文章和建议)。然而,我花了很长一段时间才弄明白,为什么它没有立即像我预期的那样工作。所以我创建了一个小测试项目来了解日志是如何工作的。
既然我已经弄明白了,我想分享我的解决方案,也许它可以帮助到别人。
我知道我的一些代码可能不是最佳实践,但我仍在学习。我把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。由__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.py中的模块记录器具有“不匹配”的命名空间my_log_test.pkg1。因此,mod1不能连接到另一个记录器,我将看不到来自mod1的日志输出。
第二个“问题”是,我的主程序在daemon.py中,而不是__main__.py中。但对我来说毕竟不是一个真正的问题,但它增加了名称空间的混乱。
工作方案
这是来自烹饪书,但在一个单独的模块。我还添加了一个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
要运行daemon .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()
要(直接)运行daemon .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()
如果有帮助我很高兴。也很高兴收到反馈!