353

我有一个具有以下结构的小型 python 项目 -

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

我计划使用默认的日志记录模块将消息打印到标准输出和日志文件。要使用日志记录模块,需要进行一些初始化 -

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

目前,我在开始记录消息之前在每个模块中执行此初始化。是否可以在一个地方只执行一次此初始化,以便通过在整个项目中记录来重用相同的设置?

4

12 回答 12

386

最佳实践是,在每个模块中,定义一个记录器,如下所示:

import logging
logger = logging.getLogger(__name__)

在模块顶部附近,然后在模块中的其他代码中执行例如

logger.debug('My message with %s', 'variable data')

如果您需要细分模块内的日志记录活动,请使用例如

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')

loggerAloggerB酌情登录。

在您的主程序或程序中,执行以下操作:

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()因为它可以更好地控制配置。

于 2013-03-31T22:09:26.920 回答
222

实际上,每个记录器都是父包记录器的子级(即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!")

有关详细信息,请参阅高级日志记录教程

于 2013-03-31T13:11:36.280 回答
33

对我来说,在多个模块中使用一个日志库实例的简单方法是以下解决方案:

base_logger.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")
于 2020-01-02T07:52:55.237 回答
19

我总是这样做如下。

使用单个 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")

这是一种简单而有效的单例模式。

于 2013-03-31T07:54:53.893 回答
13

抛出另一个解决方案。

在我的模块的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__)

当日志丢失时,您可以通过它们来自的模块来区分它们的来源。

于 2015-11-12T01:11:58.310 回答
12

其中一些答案表明您在模块的顶部

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')    

此外,如果您在 main 中使用 fileconfig,请设置 disable_existing_loggers=False,以防您的库设计人员使用模块级记录器实例。

于 2016-03-18T11:34:02.290 回答
5

你也可以想出这样的东西!

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")
于 2013-11-11T18:29:24.230 回答
5

@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 可以是整个应用程序的可插拔。希望它有意义和有价值。

于 2014-01-06T09:28:23.513 回答
2

有几个答案。我最终得到了一个对我有意义的类似但不同的解决方案,也许它对你也有意义。我的主要目标是能够按级别将日志传递给处理程序(调试级别日志到控制台,警告及以上到文件):

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 的漂亮 util 文件:

import logging

def get_logger(name):
    return logging.getLogger("flask.app." + name)

flask.app 是烧瓶中的硬编码值。应用程序记录器始终以 flask.app 作为其模块名称。

现在,在每个模块中,我都可以在以下模式下使用它:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

这将以最少的努力为“app.flask.MODULE_NAME”创建一个新日志。

于 2018-12-19T10:13:09.780 回答
2

最佳实践是单独创建一个模块,该模块只有一个方法,我们的任务是为调用方法提供一个记录器处理程序。将此文件另存为 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')
于 2019-06-06T09:39:23.557 回答
2

我想添加我的解决方案(基于记录食谱和该线程的其他文章和建议。但是我花了很长时间才弄清楚为什么它没有立即按我的预期工作。所以我创建了一个测试项目以了解日志记录是如何工作的。

既然我已经弄清楚了,我想分享我的解决方案,也许它可以对某人有所帮助。

我知道我的一些代码可能不是最佳实践,但我仍在学习。当我使用它们时,我将这些功能留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.pymod1.pymod2.py

为了使几个模块中的这个初始化更容易,我创建了my_logger.py哪个,这在食谱中有所描述。

我的错

logger = logging.getLogger(__name__)之前,我在该模块中的错误是使用(module logger) 而不是使用logger = logging.getLogger()(获取记录器)来初始化记录器。

第一个问题是,当从daemon.py记录器的命名空间调用时被设置为my_log_test.common.my_logger. mod1.py因此,具有“不匹配”命名空间的模块记录器my_log_test.pkg1.mod1无法附加到另一个记录器,并且我看不到 mod1 的日志输出。

第二个“问题”是,我的主程序是 indaemon.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

运行 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()

如果有帮助,我很高兴。也很高兴收到反馈!

于 2021-11-02T18:06:51.663 回答
1

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]
  2. 程序入口点的记录器名称将是__main__,但任何使用的解决方案__name__都会有这个问题。
于 2019-10-14T10:33:08.763 回答