简体   繁体   English

如何编写自定义 python 日志处理程序?

[英]How to write custom python logging handler?

How to write custom console log function to output only on the console window log messages on a single line (not append) until the first regular log record.如何编写自定义控制台日志功能以仅在控制台窗口上单行输出日志消息(不附加),直到第一个常规日志记录。

progress = ProgressConsoleHandler()
console  = logging.StreamHandler()  

logger = logging.getLogger('test')
logger.setLevel(logging.DEBUG) 
logger.addHandler(console)  
logger.addHandler(progress)

logger.info('test1')
for i in range(3):
    logger.progress('remaining %d seconds' % i)
    time.sleep(1)   
logger.info('test2')

So that the console output is only three lines:这样控制台输出只有三行:

INFO: test1
remaining 0 seconds... 
INFO: test2

Any suggestions on the best way on how to implement this?关于如何实现这一点的最佳方法的任何建议?

import logging
class ProgressConsoleHandler(logging.StreamHandler):
    """
    A handler class which allows the cursor to stay on
    one line for selected messages
    """
    on_same_line = False
    def emit(self, record):
        try:
            msg = self.format(record)
            stream = self.stream
            same_line = hasattr(record, 'same_line')
            if self.on_same_line and not same_line:
                stream.write(self.terminator)
            stream.write(msg)
            if same_line:
                stream.write('... ')
                self.on_same_line = True
            else:
                stream.write(self.terminator)
                self.on_same_line = False
            self.flush()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)
if __name__ == '__main__':
    import time
    progress = ProgressConsoleHandler()
    console  = logging.StreamHandler()  

    logger = logging.getLogger('test')
    logger.setLevel(logging.DEBUG) 
    logger.addHandler(progress)

    logger.info('test1')
    for i in range(3):
        logger.info('remaining %d seconds', i, extra={'same_line':True})
        time.sleep(1)   
    logger.info('test2')

Notice that only one handler is being registered, and the extra keyword argument to let the handler know it should stay on one line.请注意,只注册了一个处理程序,并且让处理程序知道它应该保留在一行的extra关键字参数。 There is more logic in the emit() method to handle changes between messages that should stay on one line and messages that need to have their own line.emit()方法中有更多的逻辑来处理应该保留在一行的消息和需要有自己的行的消息之间的变化。

Disclaimer 1免责声明 1

I have not thoroughly tested this solution, but it seems to work for basic logging functionality.我没有彻底测试过这个解决方案,但它似乎适用于基本的日志记录功能。 Also, the way of implementing it is definitely not best practice.此外,实现它的方式绝对不是最佳实践。 For these reasons, I don't recommend using this solution in production... at least without further testing由于这些原因,我不建议在生产中使用此解决方案……至少无需进一步测试

Disclaimer 2免责声明 2

Part of the reason this approach is not recommended/not best practice is because you can very easily accidentally override a method of the logging.Logger class in your wrapper class.这种方法不被推荐/不是最佳实践的部分原因是你很容易在你的包装类中意外地覆盖logging.Logger类的方法。 If this happens, it can lead to strange, hard to explain bugs.如果发生这种情况,可能会导致奇怪的、难以解释的错误。

If using this approach, take care to check that any attribute/method names you plan to use in your own custom methods/attributes do not already exist in the logging.Logger class.如果使用这种方法,请注意检查您计划在自己的自定义方法/属性中使用的任何属性/方法名称是否已存在于logging.Logger类中。

Refer to the documentation for the logging module for more information有关详细信息,请参阅logging模块的文档

Without further ado: The answer事不宜迟:答案

Answer回答

I created a base class for custom loggers as such:我为自定义记录器创建了一个基类,如下所示:

import logging

class CustomLoggerBC():
    def __init__(self, name:str):
         self._logger = logging.getLogger(name)
         newdict = {k: getattr(self._logger, k) for k in dir(self._logger) if k not in dir(self)}
         self.__dict__.update(newdict)

You then can write your own custom logger that inherits from the base class CustomLoggerBC as such:然后,您可以编写自己的自定义记录器,该记录器继承自基类CustomLoggerBC ,如下所示:

class MyCustomLogger(CustomLoggerBC):
    def __init__(name:str, ...rest of your arguments here):
        super().__init__(name)
        ... rest of your custom constructor here

In your class constructor or any method in your class you can make any desired changes you want or would normally make to the logging.Logger instance stored in self._logger such as adding a handler or setting the log level:在您的类构造函数或类中的任何方法中,您可以对存储在self._logger中的logging.Logger实例进行您想要或通常会进行的任何所需更改,例如添加处理程序或设置日志级别:

class MyCustomLogger(CustomLoggerBC):
    def __init__(name:str, ...rest of your arguments here):
        super().__init__(name)
        ... Define your handler here
        self._logger.addHandler(self._customHandler)
        self._logger.setLevel(logging.INFO)
        ... rest of your custom constructor here

Examples例子

Code like this:像这样的代码:

cl = MyCustomLogger("MyCustomLogger")
cl.info("This is a test")
cl.info("This is also a test")

Could easily produce something like this可以很容易地产生这样的东西

[ INFO ] [05/19/2022 10:03:45] This is a test [信息] [05/19/2022 10:03:45] 这是一个测试

[ INFO ] [05/19/2022 10:03:45] This is also a test [ INFO ] [05/19/2022 10:03:45] 这也是一个测试

Additionally, you can even override the methods of the logging.Logger class ( although I don't recommend it )此外,您甚至可以覆盖logging.Logger类的方法(虽然我不推荐它

class MyCustomLogger(CustomLoggerBC):
    def __init__(name:str, ...rest of your arguments here):
        super().__init__(name)
        ... rest of your custom constructor here
    def info(self, msg):
        print("My custom implementation of logging.Logger's info method!")

Code like this:像这样的代码:

cl = MyCustomLogger("MyCustomLogger")
cl.info("This is a test")
cl.info("This is also a test")

Now instead produces something like this现在改为产生这样的东西

My custom implementation of logging.Logger's info method!我的 logging.Logger 的 info 方法的自定义实现!

My custom implementation of logging.Logger's info method!我的 logging.Logger 的 info 方法的自定义实现!

Why Would You Want to Do This?你为什么想做这个?

You can preconfigure your loggers ahead of time.您可以提前预配置您的记录器。 For instance, if you know the logger for part A of your application will have a certain format, a certain handler, and a certain level.例如,如果您知道应用程序 A 部分的记录器将具有特定格式、特定处理程序和特定级别。 It is much easier to do something like this in part A在 A 部分做这样的事情要容易得多

myPartALogger = PartACustomLogger()

instead of doing all the initializing work for the logger in part A.而不是在 A 部分中为记录器做所有的初始化工作。

It also makes loggers more reusable if part A and part B need separate loggers but would have the same configuration (say for example same level and formatter but a different handle) you can create two instances of your PartACustomLogger for each part with different names and pass different handlers to each instance.如果 A 部分和 B 部分需要单独的记录器但具有相同的配置(例如相同的级别和格式化程序但不同的句柄),它还可以使记录器更可重用,您可以为每个具有不同名称的部分创建 PartACustomLogger 的两个实例并通过每个实例的不同处理程序。

Why it Works为什么有效

Essentially you are wrapping the logging.Logger class with MyCustomLogger .本质上,您正在用MyCustomLogger包装logging.Logger类。 The base class CustomLoggerBC updates the subclass's implementation instance dictionary with all the methods and attributes of the logging.Logger class so that the implementation essentially functions as the logging.Logger object.基类CustomLoggerBC使用logging.Logger类的所有方法和属性更新子类的实现实例字典,以便实现本质上用作logging.Logger对象。 Any attribute/method requests to your custom logger class MyCustomLogger appear to be forwarded to the logging.Logger instance to be resolved by the logging.Logger instance, but in reality your own class is resolving the requests.对您的自定义记录器类MyCustomLogger的任何属性/方法请求似乎都被转发到logging.Logger实例以由logging.Logger实例解析,但实际上您自己的类正在解析请求。 This makes so that it seems you are subclassing the logging.Logger class but in reality you aren't.这使得您似乎正在logging.Logger类,但实际上您不是。

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

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