【问题标题】:Redirect Python 'print' output to Logger将 Python 的“打印”输出重定向到 Logger
【发布时间】:2012-06-22 20:34:42
【问题描述】:

我有一个 Python 脚本,它使用“打印”来打印到标准输出。我最近通过 Python Logger 添加了日志记录,并希望这样做,如果启用了日志记录,这些打印语句将转到记录器。我不想修改或删除这些打印语句。

我可以通过 'log.info("some info msg")' 来记录。我希望能够做这样的事情:

if logging_enabled:
  sys.stdout=log.info
print("test")

如果启用了日志记录,“test”应该像我做 log.info("test") 一样被记录。如果未启用日志记录,则应将“test”打印到屏幕上。

这可能吗?我知道我可以以类似的方式将标准输出定向到文件(请参阅:redirect prints to log file

【问题讨论】:

    标签: python logging stdout


    【解决方案1】:

    你有两个选择:

    1. 打开一个日志文件并用它替换 sys.stdout,而不是一个函数:

      log = open("myprog.log", "a")
      sys.stdout = log
      
      >>> print("Hello")
      >>> # nothing is printed because it goes to the log file instead.
      
    2. 用你的日志函数替换打印:

      # If you're using python 2.x, uncomment the next line
      #from __future__ import print_function
      print = log.info
      
      >>> print("Hello!")
      >>> # nothing is printed because log.info is called instead of print
      

    【讨论】:

    • 在第一个示例中,是否可以同时在文件中和屏幕上同时具有标准输出?
    • 传递给 log.info 的参数与传递给 print 的参数含义不同。例如,file= 参数允许您打印到任何文件。如果您将打印功能替换为整个记录器,则此功能将丢失。只有打印到 stdout/stderr 应该被拦截。
    【解决方案2】:

    当然,您既可以打印到标准输出,也可以附加到日志文件,如下所示:

    # Uncomment the line below for python 2.x
    #from __future__ import print_function
    
    import logging
    
    logging.basicConfig(level=logging.INFO, format='%(message)s')
    logger = logging.getLogger()
    logger.addHandler(logging.FileHandler('test.log', 'a'))
    print = logger.info
    
    print('yo!')
    

    【讨论】:

      【解决方案3】:

      另一种方法是将记录器包装在一个对象中,该对象将对write 的调用转换为记录器的log 方法。

      Ferry Boender 就是这样做的,provided under the GPL licensea posthis website。下面的代码就是基于此,但解决了原来的两个问题:

      1. 该类没有实现程序退出时调用的flush方法。
      2. 该类不会缓冲换行符上的写入,因为 io.TextIOWrapper 对象应该会在奇数点产生换行符。
      import logging
      import sys
      
      
      class StreamToLogger(object):
          """
          Fake file-like stream object that redirects writes to a logger instance.
          """
          def __init__(self, logger, log_level=logging.INFO):
              self.logger = logger
              self.log_level = log_level
              self.linebuf = ''
      
          def write(self, buf):
              temp_linebuf = self.linebuf + buf
              self.linebuf = ''
              for line in temp_linebuf.splitlines(True):
                  # From the io.TextIOWrapper docs:
                  #   On output, if newline is None, any '\n' characters written
                  #   are translated to the system default line separator.
                  # By default sys.stdout.write() expects '\n' newlines and then
                  # translates them so this is still cross platform.
                  if line[-1] == '\n':
                      self.logger.log(self.log_level, line.rstrip())
                  else:
                      self.linebuf += line
      
          def flush(self):
              if self.linebuf != '':
                  self.logger.log(self.log_level, self.linebuf.rstrip())
              self.linebuf = ''
      
      
      logging.basicConfig(
          level=logging.DEBUG,
          format='%(asctime)s:%(levelname)s:%(name)s:%(message)s',
          filename="out.log",
          filemode='a'
      )
      
      stdout_logger = logging.getLogger('STDOUT')
      sl = StreamToLogger(stdout_logger, logging.INFO)
      sys.stdout = sl
      
      stderr_logger = logging.getLogger('STDERR')
      sl = StreamToLogger(stderr_logger, logging.ERROR)
      sys.stderr = sl
      
      

      这使您可以轻松地将所有输出路由到您选择的记录器。如果需要,您可以将 sys.stdout 和/或 sys.stderr 保存在此线程中,然后再替换它,如果您以后需要恢复它。

      【讨论】:

      • 你如何使用这个?您是否将其放置在您希望为每个可能将输出发送到stdout 的模块生成stdout 的模块中?或者,只要我在__main__ 函数中按上述方式配置,发送到stdout 的任何输出都会发送到file
      • 程序退出时崩溃:StreamToLogger 类还必须实现 flush()。
      • 有谁知道self.linebuf = '' 的用途。我看不到任何地方都在使用它。
      【解决方案4】:

      一个更简单的选择,

      import logging, sys
      logging.basicConfig(filename='path/to/logfile', level=logging.DEBUG)
      logger = logging.getLogger()
      sys.stderr.write = logger.error
      sys.stdout.write = logger.info
      

      【讨论】:

      • 此解决方案仅在您不使用 logging.StreamHandler() 将日志打印到屏幕时才有效。因为如果这样做,您将在无限循环中发送消息:流处理程序会尝试将其写入 sys.stdout.write,然后将其重定向到记录器,然后再次发送到流处理程序。
      • 您可以将sys.__stderr__ 用于StreamHandler,这个永远不会改变。
      • 它说:sys.stdout.write = details.info AttributeError: 'file' object attribute 'write' is read-only
      • @Linh 我相信你在 Python2.7 中运行。根据this,这是 2.7 中的错误。我也能够在我的 python2.7 中复制您的错误。我个人建议你切换到 Python3.x。
      • 这段代码对我来说很好,因为我在设置之前获得了对原始 sys.stdout 的引用。
      【解决方案5】:

      您确实应该这样做:通过调整您的日志记录配置以使用 print 语句或其他内容,具体取决于设置。不要覆盖print 行为,因为将来可能引入的某些设置(例如,由您或其他人使用您的模块)可能实际上会将其输出到stdout,您会遇到问题。

      有一个处理程序应该将您的日志消息重定向到正确的流(文件,stdout 或任何其他类似文件)。它被称为StreamHandler,并与logging 模块捆绑在一起。

      所以基本上在我看来你应该做,你说你不想做的事情:用实际的日志记录替换 print 语句。

      【讨论】:

      • 我将在以后的脚本中使用日志记录,但就我所做的而言,不值得花时间为记录器更新所有内容。不过,我会研究 StreamHandler。
      • @Rauffle:如你所愿。我强烈建议使用 C0deH4cker 提到的第二种解决方案,否则您可能会遇到我在回答中提到的问题。
      • 我必须使用某些将输出发送到stdout 的第三方库。我在我的应用程序中使用logging,所以我的应用程序本身没有任何打印语句。您提到StreamHandler 可以将日志消息发送到filestdoutStreamHandler 是否将来自 stdout 的输出发送到文件?
      • 是的,如果您正在处理通过 print() 不正确记录日志的 3rd 方库,这将特别有趣。我希望 print 基本上成为他们每一个 print 调用的 logging.info 。然后我会使用正确的过滤器获得时间戳、线程 ID 等。
      【解决方案6】:

      一旦你定义了你的记录器,即使有多个打印参数,也可以使用它来将打印重定向到记录器。

      print = lambda *tup : logger.info(str(" ".join([str(x) for x in tup]))) 
      

      【讨论】:

        【解决方案7】:

        下面的片段在我的 PySpark 代码中完美运行。如果有人需要以防万一理解-->

        import os
        import sys
        import logging
        import logging.handlers
        
        log = logging.getLogger(__name_)
        
        handler = logging.FileHandler("spam.log")
        formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
        handler.setFormatter(formatter)
        log.addHandler(handler)
        sys.stderr.write = log.error 
        sys.stdout.write = log.info 
        

        (将在同一目录中的“spam.log”中记录每个错误,控制台/stdout 上不会有任何内容)

        (将“spam.log”中的所有信息记录在同一目录中,控制台/stdout上不会有任何信息)

        在两个文件以及控制台中打印输出错误/信息删除上面的两行。

        快乐编码 干杯!!!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-06-26
          • 1970-01-01
          • 2015-12-16
          • 1970-01-01
          • 2011-11-01
          • 2011-05-05
          相关资源
          最近更新 更多