【问题标题】:Making Python loggers output all messages to stdout in addition to log file使 Python 记录器将除日志文件之外的所有消息输出到标准输出
【发布时间】:2012-12-13 01:35:18
【问题描述】:

有没有办法让 Python 使用 logging 模块自动将内容输出到标准输出 到它们应该去的日志文件?例如,我希望所有对logger.warninglogger.criticallogger.error 的调用都可以转到他们想要的地方,但另外总是被复制到stdout。这是为了避免重复消息,例如:

mylogger.critical("something failed")
print "something failed"

【问题讨论】:

标签: python logging error-logging


【解决方案1】:

所有日志输出都由处理程序处理;只需将logging.StreamHandler() 添加到根记录器即可。

这是一个配置流处理程序的示例(使用stdout 而不是默认的stderr)并将其添加到根记录器:

import logging
import sys

root = logging.getLogger()
root.setLevel(logging.DEBUG)

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root.addHandler(handler)

【讨论】:

  • 很好,但是如果它已经被重定向到一个文件,我怎么能把它打印到stdout呢?
  • @user248237:通过添加 new 处理程序,如图所示。新的处理程序不会替换现有的处理程序,它们可以处理日志条目。
  • @PrakharMohanSrivastava 我猜你可以将它添加到传递给logging.Formatter的字符串中。
  • @himanshu219:logger 有级别,handler 有级别。 logger 将处理该级别及更高级别的消息,handler 将处理该级别及更高级别的消息。它可以让您区分不同的记录器和不同的处理程序。
  • @himanshu219:用例是,一旦您开始添加多个处理程序,您通常想要区分。 DEBUG到控制台,WARNING到一个文件等等
【解决方案2】:

记录到标准输出的最简单方法:

import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

【讨论】:

  • 嗯,但这并没有记录到文件中,对吧?问题是如何记录到文件到控制台。
  • 至少在 Python 3 中,看起来省略 stream=sys.stdout 仍然可以为我登录到控制台。
  • @TaylorEdmiston 是的,但它是标准错误流 AFAIK。尝试从 shell 重定向输出。
  • 好的。这不能同时回答:记录到文件和控制台,但很高兴在 3 行或更少的时间内找到我需要的内容。
【解决方案3】:

您可以为文件和标准输出创建两个处理程序,然后使用handlers 参数为basicConfig 创建一个记录器。如果两个处理程序的 log_level 和格式输出相同,这可能会很有用:

import logging
import sys

file_handler = logging.FileHandler(filename='tmp.log')
stdout_handler = logging.StreamHandler(sys.stdout)
handlers = [file_handler, stdout_handler]

logging.basicConfig(
    level=logging.DEBUG, 
    format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s',
    handlers=handlers
)

logger = logging.getLogger('LOGGER_NAME')

【讨论】:

    【解决方案4】:

    可以使用多个处理程序。

    import logging
    import auxiliary_module
    
    # create logger with 'spam_application'
    log = logging.getLogger('spam_application')
    log.setLevel(logging.DEBUG)
    
    # create formatter and add it to the handlers
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    
    # create file handler which logs even debug messages
    fh = logging.FileHandler('spam.log')
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    log.addHandler(fh)
    
    # create console handler with a higher log level
    ch = logging.StreamHandler()
    ch.setLevel(logging.ERROR)
    ch.setFormatter(formatter)
    log.addHandler(ch)
    
    log.info('creating an instance of auxiliary_module.Auxiliary')
    a = auxiliary_module.Auxiliary()
    log.info('created an instance of auxiliary_module.Auxiliary')
    
    log.info('calling auxiliary_module.Auxiliary.do_something')
    a.do_something()
    log.info('finished auxiliary_module.Auxiliary.do_something')
    
    log.info('calling auxiliary_module.some_function()')
    auxiliary_module.some_function()
    log.info('done with auxiliary_module.some_function()')
    
    # remember to close the handlers
    for handler in log.handlers:
        handler.close()
        log.removeFilter(handler)
    

    请看:https://docs.python.org/2/howto/logging-cookbook.html

    【讨论】:

    • 很好的答案,虽然有点乱。喜欢你如何展示如何为流和文件使用不同的级别和格式。 +1,但精神上 +2。
    • 对我来说,如果没有 ch = logging.StreamHandler() 中的 sys.stdout 参数,这是行不通的
    【解决方案5】:

    记录到文件和标准错误的最简单方法:

    import logging
    
    logging.basicConfig(filename="logfile.txt")
    stderrLogger=logging.StreamHandler()
    stderrLogger.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
    logging.getLogger().addHandler(stderrLogger)
    

    【讨论】:

    • 这不会在控制台中的日志消息之前显示标签 INFO、DEBUG 和 ERROR。它确实在文件中显示了这些标签。在控制台中显示标签的任何想法?
    • 谢谢@JahMyst,我添加了格式化程序。不幸的是,它不再那么短了,但仍然是最简单的方法。 :-)
    【解决方案6】:

    这是一个基于功能强大但文档很少的logging.config.dictConfig method 的解决方案。 它不是将每条日志消息都发送到stdout,而是将日志级别为ERROR 和更高级别的消息发送到stderr,将其他所有消息发送到stdout。 如果系统的其他部分正在收听stderrstdout,这将很有用。

    import logging
    import logging.config
    import sys
    
    class _ExcludeErrorsFilter(logging.Filter):
        def filter(self, record):
            """Only lets through log messages with log level below ERROR (numeric value: 40)."""
            return record.levelno < 40
    
    
    config = {
        'version': 1,
        'filters': {
            'exclude_errors': {
                '()': _ExcludeErrorsFilter
            }
        },
        'formatters': {
            # Modify log message format here or replace with your custom formatter class
            'my_formatter': {
                'format': '(%(process)d) %(asctime)s %(name)s (line %(lineno)s) | %(levelname)s %(message)s'
            }
        },
        'handlers': {
            'console_stderr': {
                # Sends log messages with log level ERROR or higher to stderr
                'class': 'logging.StreamHandler',
                'level': 'ERROR',
                'formatter': 'my_formatter',
                'stream': sys.stderr
            },
            'console_stdout': {
                # Sends log messages with log level lower than ERROR to stdout
                'class': 'logging.StreamHandler',
                'level': 'DEBUG',
                'formatter': 'my_formatter',
                'filters': ['exclude_errors'],
                'stream': sys.stdout
            },
            'file': {
                # Sends all log messages to a file
                'class': 'logging.FileHandler',
                'level': 'DEBUG',
                'formatter': 'my_formatter',
                'filename': 'my.log',
                'encoding': 'utf8'
            }
        },
        'root': {
            # In general, this should be kept at 'NOTSET'.
            # Otherwise it would interfere with the log levels set for each handler.
            'level': 'NOTSET',
            'handlers': ['console_stderr', 'console_stdout', 'file']
        },
    }
    
    logging.config.dictConfig(config)
    

    【讨论】:

    • 必须将记录器重命名为空字符串才能真正获取根记录器。否则非常有帮助,谢谢!
    • 哇,以前从未意识到dictConfig的存在!!非常感谢!!!
    【解决方案7】:

    如需更详细的说明 - link 上的精彩文档。 例如:很简单,只需要设置两个logger即可。

    import sys
    import logging
    
    logger = logging.getLogger('')
    logger.setLevel(logging.DEBUG)
    fh = logging.FileHandler('my_log_info.log')
    sh = logging.StreamHandler(sys.stdout)
    formatter = logging.Formatter('[%(asctime)s] %(levelname)s [%(filename)s.%(funcName)s:%(lineno)d] %(message)s', datefmt='%a, %d %b %Y %H:%M:%S')
    fh.setFormatter(formatter)
    sh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.addHandler(sh)
    
    def hello_logger():
        logger.info("Hello info")
        logger.critical("Hello critical")
        logger.warning("Hello warning")
        logger.debug("Hello debug")
    
    if __name__ == "__main__":
        print(hello_logger())
    

    输出 - 终端:

    [Mon, 10 Aug 2020 12:44:25] INFO [TestLoger.py.hello_logger:15] Hello info
    [Mon, 10 Aug 2020 12:44:25] CRITICAL [TestLoger.py.hello_logger:16] Hello critical
    [Mon, 10 Aug 2020 12:44:25] WARNING [TestLoger.py.hello_logger:17] Hello warning
    [Mon, 10 Aug 2020 12:44:25] DEBUG [TestLoger.py.hello_logger:18] Hello debug
    None
    

    输出 - 在文件中:


    更新:彩色终端

    包装:

    pip install colorlog
    

    代码:

    import sys
    import logging
    import colorlog
    
    logger = logging.getLogger('')
    logger.setLevel(logging.DEBUG)
    fh = logging.FileHandler('my_log_info.log')
    sh = logging.StreamHandler(sys.stdout)
    formatter = logging.Formatter('[%(asctime)s] %(levelname)s [%(filename)s.%(funcName)s:%(lineno)d] %(message)s', datefmt='%a, %d %b %Y %H:%M:%S')
    fh.setFormatter(formatter)
    sh.setFormatter(colorlog.ColoredFormatter('%(log_color)s [%(asctime)s] %(levelname)s [%(filename)s.%(funcName)s:%(lineno)d] %(message)s', datefmt='%a, %d %b %Y %H:%M:%S'))
    logger.addHandler(fh)
    logger.addHandler(sh)
    
    def hello_logger():
        logger.info("Hello info")
        logger.critical("Hello critical")
        logger.warning("Hello warning")
        logger.debug("Hello debug")
        logger.error("Error message")
    
    if __name__ == "__main__":
        hello_logger()
    

    输出:

    建议:

    Complete logger configuration 来自INI 文件,其中还包括stdoutdebug.log 的设置:

    • handler_file
      • level=WARNING
    • handler_screen
      • level=DEBUG

    【讨论】:

      【解决方案8】:

      由于没有人分享一个整洁的两个班轮,我将分享我自己的:

      logging.basicConfig(filename='logs.log', level=logging.DEBUG, format="%(asctime)s:%(levelname)s: %(message)s")
      logging.getLogger().addHandler(logging.StreamHandler())
      

      【讨论】:

        【解决方案9】:

        这是一个非常简单的例子:

        import logging
        l = logging.getLogger("test")
        
        # Add a file logger
        f = logging.FileHandler("test.log")
        l.addHandler(f)
        
        # Add a stream logger
        s = logging.StreamHandler()
        l.addHandler(s)
        
        # Send a test message to both -- critical will always log
        l.critical("test msg")
        

        输出将在标准输出和文件中显示“test msg”。

        【讨论】:

          【解决方案10】:

          我简化了我的源代码(其原始版本是 OOP 并使用配置文件),为您提供@EliasStrehle 的替代解决方案,而不使用 dictConfig(因此最容易与现有源代码集成):

          import logging
          import sys
          
          
          def create_stream_handler(stream, formatter, level, message_filter=None):
              handler = logging.StreamHandler(stream=stream)
              handler.setLevel(level)
              handler.setFormatter(formatter)
              if message_filter:
                  handler.addFilter(message_filter)
              return handler
          
          
          def configure_logger(logger: logging.Logger, enable_console: bool = True, enable_file: bool = True):
              if not logger.handlers:
                  if enable_console:
                      message_format: str = '{asctime:20} {name:16} {levelname:8} {message}'
                      date_format: str = '%Y/%m/%d %H:%M:%S'
                      level: int = logging.DEBUG
                      formatter = logging.Formatter(message_format, date_format, '{')
          
                      # Configures error output (from Warning levels).
                      error_output_handler = create_stream_handler(sys.stderr, formatter,
                                                                   max(level, logging.WARNING))
                      logger.addHandler(error_output_handler)
          
                      # Configures standard output (from configured Level, if lower than Warning,
                      #  and excluding everything from Warning and higher).
                      if level < logging.WARNING:
                          standard_output_filter = lambda record: record.levelno < logging.WARNING
                          standard_output_handler = create_stream_handler(sys.stdout, formatter, level,
                                                                          standard_output_filter)
                          logger.addHandler(standard_output_handler)
          
                  if enable_file:
                      message_format: str = '{asctime:20} {name:16} {levelname:8} {message}'
                      date_format: str = '%Y/%m/%d %H:%M:%S'
                      level: int = logging.DEBUG
                      output_file: str = '/tmp/so_test.log'
          
                      handler = logging.FileHandler(output_file)
                      formatter = logging.Formatter(message_format, date_format, '{')
                      handler.setLevel(level)
                      handler.setFormatter(formatter)
                      logger.addHandler(handler)
          

          这是一个非常简单的测试方法:

          logger: logging.Logger = logging.getLogger('MyLogger')
          logger.setLevel(logging.DEBUG)
          configure_logger(logger, True, True)
          logger.debug('Debug message ...')
          logger.info('Info message ...')
          logger.warning('Warning ...')
          logger.error('Error ...')
          logger.fatal('Fatal message ...')
          

          【讨论】:

            猜你喜欢
            • 2017-04-13
            • 2016-09-24
            • 2021-10-31
            • 2011-09-18
            • 1970-01-01
            • 2019-04-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多