简体   繁体   English

Python 在不同的日志级别记录到多个处理程序?

[英]Python logging to multiple handlers, at different log levels?

Folks,各位,

I'm scratching my head on a python logging config that I can't get right.我正在为一个我无法正确处理的 python 日志配置而挠头。

Let's say I have the following package installed:假设我安装了以下软件包:

mypackage/
   data/mypackage.logging.conf
   module1.py
   module2.py
   scripts/main.py

As the script can be used interactively or run from a crontab, I have the following requirements:由于脚本可以交互使用或从 crontab 运行,我有以下要求:

  1. no print statements, everything goes through logging;没有打印语句,一切都经过日志记录;

  2. log using a timedRotatingFileHandler , always at DEBUG level;使用timedRotatingFileHandler记录日志,始终处于调试级别;

  3. log using a mailinglogger.SummarisingLogger , always at INFO level;使用mailinglogger.SummarisingLogger总是在 INFO 级别;

  4. log to console, with level set by default to INFO or overridden through a command-line option.登录到控制台,级别默认设置为 INFO 或通过命令行选项覆盖。

Problem is, I can change the log level through the command-line and the console log level is changed accordingly, but the other handlers are also changed, which I don't want... :-/问题是,我可以通过命令行更改日志级别并相应地更改控制台日志级别,但其他处理程序也发生了更改,这是我不想要的... :-/

In a logging config file, I'm not sure I understand the precedence between the root logger's level, other loggers' level and handlers' level settings.在日志记录配置文件中,我不确定我是否理解根记录器的级别、其他记录器的级别和处理程序的级别设置之间的优先级。

Here is some sample code.这是一些示例代码。 Any clues will be appreciated :-)任何线索将不胜感激:-)

# mypackage/data/mypackage.logging.conf
[loggers]
root,mypackage

[handlers]
keys=consoleHandler,timedRotatingFileHandler,summarisingHandler

[formatters]
keys=simpleFormatter,consoleFormatter,mypackageFormatter

[logger_root]
#level=INFO
handlers=consoleHandler

[logger_mypackage]
#level=INFO
handlers=timedRotatingFileHandler,summarisingHandler
qualname=mypackage

[handler_consoleHandler]
class=StreamHandler
#level=INFO
formatter=consoleFormatter
args=(sys.stdout,)

[handler_timedRotatingFileHandler]
class=logging.handlers.TimedRotatingFileHandler
level=DEBUG
formatter=mypackageFormatter
args=('mypackage.log', 'M', 1, 5)

[handler_summarisingHandler]
class=mailinglogger.SummarisingLogger
level=INFO
formatter=mypackageFormatter
args=('mypackage@someserver.somewhere.com', ('mypackage-alerts@somewhere.com',), 'relay.somewhere.com')

#mypackage/scripts/main.py:
import logging
import logging.config
import os
import sys

import mypackage.module1
import mypackage.module2

logging.config.fileConfig('data/mypackage.logging.conf')
log = logging.getLogger(__name__)

if __name__ == '__main__':
    loglevel = 'INFO'
    if len(sys.argv) > 1:
        loglevel = sys.argv[1].upper()

    logging.getLogger('').setLevel(getattr(logging, loglevel))
    # or logging.getLogger('mypackage').setLevel(getattr(logging, loglevel)) ?

    mypackage.module1.do_something()
    mypackage.module2.do_something_else()

#mypackage/module1.py:
import logging

log = logging.getLogger(__name__)
log.addHandler(NullHandler())

def do_something():
    log.debug("some debug message from:" + __name__)
    log.info("some info message from:" + __name__)
    log.error("some error message from:" + __name__)

#mypackage/module2.py:
import logging

log = logging.getLogger(__name__)
log.addHandler(NullHandler())

def do_something_else():
    log.debug("some debug message from:" + __name__)
    log.info("some info message from:" + __name__)
    log.error("some error message from:" + __name__)

UPDATE 1更新 1

In the meantime, I discovered this answer and successfully modified my code this way:与此同时,我发现了这个答案并通过这种方式成功修改了我的代码:

#mypackage/scripts/main.py:
import logging
import logging.config
import os
import sys
import mailinglogger

import mypackage.module1
import mypackage.module2

def main():
    # get the console log level from the command-line
    loglevel = 'INFO'
    if len(sys.argv) > 1:
        loglevel = sys.argv[1].upper()

    # create formatters
    simple_formatter = logging.Formatter("%(name)s:%(levelname)s: %(message)s")
    detailed_formatter = logging.Formatter("%(asctime)s %(name)s[%(process)d]: %(levelname)s - %(message)s")

    # get a top-level "mypackage" logger,
    # set its log level to DEBUG,
    # BUT PREVENT IT from propagating messages to the root logger
    #
    log = logging.getLogger('mypackage')
    log.setLevel(logging.DEBUG)
    log.propagate = 0

    # create a console handler
    # and set its log level to the command-line option 
    # 
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setLevel(getattr(logging, loglevel))
    console_handler.setFormatter(simple_formatter)

    # create a file handler
    # and set its log level to DEBUG
    #
    file_handler = logging.handlers.TimedRotatingFileHandler('mypackage.log', 'M', 1, 5)
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(detailed_formatter)

    # create a mail handler
    # and set its log level to INFO
    #
    mail_handler = mailinglogger.SummarisingLogger(
        'mypackage@someserver.somewhere.com', ('mypackage-alerts@somewhere.com',), 'relay.somewhere.com')
    mail_handler.setLevel(logging.INFO)
    mail_handler.setFormatter(detailed_formatter)

    # add handlers to the "mypackage" logger
    #
    log.addHandler(console_handler)
    log.addHandler(file_handler)
    log.addHandler(mail_handler)

    # let the modules do their stuff 
    # and log to the "mypackage.module1" and "mypackage.module2" loggers
    #
    mypackage.module1.do_something()
    mypackage.module2.do_something_else()


if __name__ == '__main__':
    main()

Now, I'll try to translate this in a logging.config file...现在,我将尝试在 logging.config 文件中翻译它...


UPDATE 2更新 2

Here is the best logging config and code combination I found.这是我找到的最好的日志配置和代码组合。

In the mypackage.logging.conf file, the "mypackage" logger is:在 mypackage.logging.conf 文件中,“mypackage”记录器是:

  • set up only with the file and email handlers;仅使用文件和电子邮件处理程序进行设置;
  • its propagate is set to false;它的传播设置为false;
  • its level is set to DEBUG;其级别设置为调试;
  • while the file and email handlers are respectively set to INFO and DEBUG.而文件和电子邮件处理程序分别设置为 INFO 和 DEBUG。

#mypackage/data/mypackage.logging.conf
[loggers]
keys=root,mypackage

[handlers]
keys=consoleHandler,timedRotatingFileHandler,summarisingHandler

[formatters]
keys=simpleFormatter,consoleFormatter,mypackageFormatter

[logger_root]
#level=INFO
handlers=consoleHandler

[logger_mypackage]
level=DEBUG
handlers=timedRotatingFileHandler,summarisingHandler
qualname=mypackage
propagate=0

[handler_consoleHandler]
class=StreamHandler
#level=INFO
formatter=consoleFormatter
args=(sys.stdout,)

[handler_timedRotatingFileHandler]
class=logging.handlers.TimedRotatingFileHandler
level=DEBUG
formatter=mypackageFormatter
args=('mypackage.log', 'M', 1, 5)

[handler_summarisingHandler]
class=mailinglogger.SummarisingLogger
level=INFO
formatter=mypackageFormatter
args=('mypackage@someserver.somewhere.com', ('mypackage-alerts@somewhere.com',), 'relay.somewhere.com')

[formatter_consoleFormatter]
format=%(levelname)s: %(message)s
datefmt=

[formatter_mypackageFormatter]
format=%(asctime)s %(name)s[%(process)d]: %(levelname)s - %(message)s
datefmt=

In the script:在脚本中:

  1. the logging config is read;读取日志配置;

  2. a console_formatter is (re-)created;一个 console_formatter 是(重新)创建的;

  3. a console handler is created with the log level from the command-line option, then added to the "mypackage" logger.使用命令行选项中的日志级别创建控制台处理程序,然后将其添加到“mypackage”记录器中。


import logging
import logging.config
import os
import sys

import mypackage.module1
import mypackage.module2

def setup_logging(loglevel):
    #
    # load logging config from file
    #
    logging.config.fileConfig('data/mypackage.logging.conf')

    # (re-)create formatter
    console_formatter = logging.Formatter("%(name)s:%(levelname)s: %(message)s")

    # create a console handler
    # and set its log level to the command-line option 
    # 
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setFormatter(console_formatter)
    console_handler.setLevel(getattr(logging, loglevel))

    # add console handler to the pre-configured "mypackage" logger
    #
    logger = logging.getLogger('mypackage')
    logger.addHandler(console_handler)


def main():
    # get the console log level from the command-line
    loglevel = 'INFO'
    if len(sys.argv) > 1:
        loglevel = sys.argv[1].upper()

    # load logging config and setup console handler
    #
    setup_logging(loglevel)

    # log from the script to the "mypackage.scripts.main" logger
    #
    log = logging.getLogger(__name__)
    log.debug("some debug message from:" + __name__)
    log.info("some info message from:" + __name__)
    log.error("some error message from:" + __name__)

    # let the modules do their stuff 
    # and log to the "mypackage.module1" and "mypackage.module2" loggers
    #
    mypackage.module1.do_something()
    mypackage.module2.do_something_else()

if __name__== '__main__':
    main()

Things would be simpler if the handlers were "addressable" by name when loaded from a config file.如果处理程序在从配置文件加载时按名称“可寻址”,事情会更简单。

Then, we could have the mypackage console handler set up in the config file and its log level changed in the code like this:然后,我们可以在配置文件中设置 mypackage 控制台处理程序,并在代码中更改其日志级别,如下所示:

def setup_logging(loglevel):
    logging.config.fileConfig('data/mypackage.logging.conf')

    logger = logging.getLogger('mypackage')
    console_handler = logger.getHandler('consoleHandler')
    console_handler.setLevel(getattr(logging, loglevel))

There would no need to re-create a formatter either...也不需要重新创建格式化程序......

(last update: yes, I'm aware of https://docs.python.org/3/library/logging.config.html#incremental-configuration , but in this case, I'm stuck with Python 2.6... :-) (最后更新:是的,我知道https://docs.python.org/3/library/logging.config.html#incremental-configuration ,但在这种情况下,我坚持使用 Python 2.6 ... :-)

Use dictConfig.使用 dictConfig。 Here is an example of logging to multiple files with separate handles in dictConfig.这是在 dictConfig 中使用单独句柄记录到多个文件的示例。 This isn't exactly what you are looking for, but you could modify this example and simply change the level of each handler that you are looking to use.这并不完全是您要查找的内容,但是您可以修改此示例并简单地更改您要使用的每个处理程序的级别。

import os, logging
from logging.config import dictConfig

FORMAT = "%(asctime)s {app} [%(thread)d] %(levelname)-5s %(name)s - %(message)s. [file=%(filename)s:%(lineno)d]"
DATE_FORMAT = None


def setup_logging(name, level="INFO", fmt=FORMAT):
    formatted = fmt.format(app=name)
    log_dir = r'C:/log_directory'
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)

    logging_config = {
        "version": 1,
        'disable_existing_loggers': False,
        "formatters": {
            'standard': {
                'format': formatted
            }
        },
        "handlers": {
            'default': {
                'class': 'logging.StreamHandler',
                'formatter': 'standard',
                'level': level,
                'stream': 'ext://sys.stdout'
            },
            'file': {
                'class': 'logging.handlers.TimedRotatingFileHandler',
                'when': 'midnight',
                'utc': True,
                'backupCount': 5,
                'level': level,
                'filename': '{}/log1.log'.format(log_dir),
                'formatter': 'standard',
            },
            'file2': {
                'class': 'logging.handlers.TimedRotatingFileHandler',
                'when': 'midnight',
                'utc': True,
                'backupCount': 5,
                'level': level,
                'filename': '{}/log2.log'.format(log_dir),
                'formatter': 'standard',
            }
        },
        "loggers": {
            "": {
                'handlers': ['default', 'file'],
                'level': level
            },
            "second_log": {
                'handlers': ['default', 'file2'],
                'level': level
            }
        }
    }

    dictConfig(logging_config)

log.setup_logging(name="log-name", level=LEVEL
logger = logging.getLogger(__name__)
second_logger = logging.getLogger('second_log')
second_logger.propagate = False

An approach to update your handler:一种更新处理程序的方法:

import logging

from rootmodule.mymodule import mylogger

def update_handler_level(logger,  handler_type, level="INFO"):
    # if not root logger user logger.parent
    for handler in logger.handlers or logger.parent.handlers:
        if isinstance(handler, handler_type):
            print(handler.level)
            handler.setLevel(getattr(logging, level, "INFO"))
            print(handler.level)

mylogger.debug('test')
update_handler_level(mylogger, logging.StreamHandler)
mylogger.debug('test')

My logging.cfg is pretty similar than your except the fact that the logger name si set in a constant module (a can do module rename without breaking the logging configuration)我的 logging.cfg 与您的非常相似,除了记录器名称 si 设置在常量模块中(可以在不破坏日志记录配置的情况下进行模块重命名)

To update from command line, you must have a mapping between your opts value and logging.Handler sub class name.要从命令行更新,您的 opts 值和 logging.Handler 子类名称之间必须有一个映射。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM