如何使用不同的类和导入动态地使用 Python 日志更改文件句柄

Posted

技术标签:

【中文标题】如何使用不同的类和导入动态地使用 Python 日志更改文件句柄【英文标题】:How to change filehandle with Python logging on the fly with different classes and imports 【发布时间】:2012-11-30 03:28:53 【问题描述】:

我无法执行动态记录文件句柄更改。

例如,我有 3 个班级

one.py

import logging
class One():
    def __init__(self,txt="?"):
        logging.debug("Hey, I'm the class One and I say: %s" % txt)

two.py

import logging
class Two():
    def __init__(self,txt="?"):
        logging.debug("Hey, I'm the class Two and I say: %s" % txt)

config.py

import logging
class Config():
    def __init__(self,logfile=None):
        logging.debug("Reading config")
        self.logfile(logfile)

myapp

from one import One
from two import Two
from config import Config
import logging

#Set default logging
logging.basicConfig( 
    level=logging.getLevelName(DEBUG), 
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename=None
)

logging.info("Starting with stdout")

o=One(txt="STDOUT")
c=Config(logfile="/tmp/logfile")

# Here must be the code that change the logging configuration and set the filehandler

t=One(txt="This must be on the file, not STDOUT")

如果我再次尝试loggin.basicConfig(),它不起作用。

【问题讨论】:

【参考方案1】:

确实,如果已经设置了处理程序,logging.basicConfig什么都不做

如果根记录器已经为其配置了处理程序,则此函数不执行任何操作。

您需要替换根记录器上的当前处理程序:

import logging

fileh = logging.FileHandler('/tmp/logfile', 'a')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fileh.setFormatter(formatter)

log = logging.getLogger()  # root logger
for hdlr in log.handlers[:]:  # remove all old handlers
    log.removeHandler(hdlr)
log.addHandler(fileh)      # set the new handler

请参阅 Python Logging HOWTO 中的 Configuring Logging chapter。

【讨论】:

@alper:下一个选项:创建每个线程的处理程序,为每个处理程序提供一个过滤器,用于过滤日志记录 thread 属性。将过滤器附加到任何其他处理程序,该处理程序为设置了thread 的日志记录返回 False。 ♦ 谢谢,我也在这里问过 (***.com/q/61818674/2402577) 我会将其链接到您的评论。【参考方案2】:

我找到了比上述“接受”答案更简单的方法。如果您有对处理程序的引用,您需要做的就是调用 close() 方法,然后设置 baseFilename 属性。分配 baseFilename 时,请务必使用 os.path.abspath()。库源中有一条注释表明它是必需的。我将配置内容保存在全局 dict() 中,因此很容易保存 FileHandler 引用对象。正如您在下面看到的,只需要 2 行代码即可动态更改处理程序的日志文件名。

import logging

def setup_logging():
  global config

  if config['LOGGING_SET']:
    config['LOG_FILE_HDL'].close()
    config['LOG_FILE_HDL'].baseFilename = os.path.abspath(config['LOG_FILE'])

    config['DEBUG_LOG_HDL'].close()
    config['DEBUG_LOG_HDL'].baseFilename = os.path.abspath(config['DEBUG_LOG'])
  else:
    format_str = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(format_str)

    log = logging.getLogger()

    log.setLevel(logging.DEBUG)

    # add file mode="w" to overwrite
    config['LOG_FILE_HDL'] = logging.FileHandler(config['LOG_FILE'], mode='a')
    config['LOG_FILE_HDL'].setLevel(logging.INFO)
    config['LOG_FILE_HDL'].setFormatter(formatter)
    log.addHandler(config['LOG_FILE_HDL'])

    # the delay=1 should prevent the file from being opened until used.
    config['DEBUG_LOG_HDL'] = logging.FileHandler(config['DEBUG_LOG'], mode='a', delay=1)
    config['DEBUG_LOG_HDL'].setLevel(logging.DEBUG)
    config['DEBUG_LOG_HDL'].setFormatter(formatter)
    log.addHandler(config['DEBUG_LOG_HDL'])

    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    log.addHandler(ch)
    config['LOGGING_SET'] = True

【讨论】:

即使您不保存对处理程序的引用,如果您的记录器中只有一个处理程序,您也可以直接引用它并使用相同的技术:mylogger = logging.getLogger('mylogger') # get logger 然后mylogger.handlers[0].close() mylogger.handlers[0].baseFilename = os.path.abspath('newfilename.log') @user2179204 你伤心“更容易”并且写了四倍多的代码行 - 这不是 python 方式。【参考方案3】:

@Martijn Pieters 提供的答案效果很好。但是,代码剪辑器删除了所有处理程序并仅将文件处理程序放回原处。如果您的应用程序有其他模块添加的处理程序,这将很麻烦。

因此,下面的 sn-p 被设计为仅替换文件处理程序。

if isinstance(hdlr,logging.FileHandler) 这行是关键。

import logging

filehandler = logging.FileHandler('/tmp/logfile', 'a')
formatter = logging.Formatter('%(asctime)-15s::%(levelname)s::%(filename)s::%(funcName)s::%(lineno)d::%(message)s')
filehandler.setFormatter(formatter)
log = logging.getLogger()  # root logger - Good to get it only once.
for hdlr in log.handlers[:]:  # remove the existing file handlers
    if isinstance(hdlr,logging.FileHandler):
        log.removeHandler(hdlr)
log.addHandler(filehandler)      # set the new handler
# set the log level to INFO, DEBUG as the default is ERROR
log.setLevel(logging.DEBUG)

【讨论】:

【参考方案4】:

我尝试将@Martijn Pieters 和@Arun Thundyill Saseendran 在此页面上的建议结合起来。我太新了,不能发表评论,所以我必须发布一个调整后的答案。在 isinstance 调用中,我必须使用“logging”而不是“log”来访问类型(log 是一个实例),然后“FileHander”应该是“FileHandler”。我正在使用 Python 3.6。

import logging

filehandler = logging.FileHandler('/tmp/logfile', 'a')
formatter = logging.Formatter('%(asctime)-15s::%(levelname)s::%(filename)s::%(funcName)s::%(lineno)d::%(message)s')
filehandler.setFormatter(formatter)
log = logging.getLogger()  # root logger - Good to get it only once.
for hdlr in log.handlers[:]:  # remove the existing file handlers
    if isinstance(hdlr,logging.FileHandler): #fixed two typos here
        log.removeHandler(hdlr)
log.addHandler(filehandler)      # set the new handler
# set the log level to INFO, DEBUG as the default is ERROR
logging.setLevel(log.DEBUG)      

【讨论】:

很好的解决方案!我正在使用 Python 烧瓶,它让我可以将消息保存在烧瓶日志中。谢谢。

以上是关于如何使用不同的类和导入动态地使用 Python 日志更改文件句柄的主要内容,如果未能解决你的问题,请参考以下文章

python中函数和方法区别,以及如何给python类动态绑定方法和属性(涉及types.MethodType()和__slots__)

复习打卡--0819元类和内存管理

Python中的类和方法使用举例

OOP1(定义基类和派生类)

使用元类动态设置属性

有条件地导入 python 类的片段