在多个模块中使用日志记录

2025-02-12 10:03:00
admin
原创
50
摘要:问题描述:我有一个小型的 Python 项目,其结构如下 -Project -- pkg01 -- test01.py -- pkg02 -- test02.py -- logging.conf 我计划使用默认日志模块将消息打印到 stdout 和日志文件。要使用日志模块,需要进行一些初始...

问题描述:

我有一个小型的 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.pymod2.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!!!")

注意事项:

  1. 您必须将文件作为模块运行,否则import [your module]将无法工作:

* `python -m [your module name].[your filename without .py]`
  1. 程序入口点的记录器名称将是__main__,但任何使用的解决方案__name__都会有该问题。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用