知识点1:logger间存在继承关系

logger 通过名字来决定继承关系,如果一个logger的名字是"mydest",另一个logger的名字是"mydest.dest1" getLogger("mydest.dest1")),那么就称后者是前者的子logger,会继承前者的配置。上面的代码没有指定logger,直接调用logging.debug等方法时,会使用所有logger的祖先类RootLogger

从上面的代码运行结果可以猜测出,该RootLogger设置的日志级别是logging.WARN,输出目的地是标准流。从源码可以更清楚的看出来:

  1. root = RootLogger(WARNING)  #设置WARNING的级别 

至于rootLogger的输出目的地的配置,我们跟踪logging.debug的源代码来看一下:

  1. def debug(msg, *args, **kwargs): 
  2.  
  3. ""
  4.  
  5. Log a message with severity 'DEBUG' on the root logger. 
  6.  
  7. ""
  8.  
  9. if len(root.handlers) == 0
  10.  
  11. basicConfig() 
  12.  
  13. root.debug(msg, *args, **kwargs) 

大约可以看到,如果rootLogger没有配置handler,就会不带参数运行basicConfig函数(*请看知识点2),我们看一下basicConfig的源代码:

  1. def basicConfig(**kwargs): 
  2.  
  3. _acquireLock() 
  4.  
  5. try
  6.  
  7. if len(root.handlers) == 0
  8.  
  9. filename = kwargs.get("filename"
  10.  
  11. if filename: 
  12.  
  13. mode = kwargs.get("filemode"'a'
  14.  
  15. hdlr = FileHandler(filename, mode) 
  16.  
  17. else
  18.  
  19. stream = kwargs.get("stream"
  20.  
  21. hdlr = StreamHandler(stream) 
  22.  
  23. fs = kwargs.get("format", BASIC_FORMAT) 
  24.  
  25. dfs = kwargs.get("datefmt"None
  26.  
  27. fmt = Formatter(fs, dfs) 
  28.  
  29. hdlr.setFormatter(fmt) 
  30.  
  31. root.addHandler(hdlr) 
  32.  
  33. level = kwargs.get("level"
  34.  
  35. if level is not None
  36.  
  37. root.setLevel(level) 
  38.  
  39. finally
  40.  
  41. _releaseLock() 

因 为参数为空,所以我们就看出了,该rootLoger使用了不带参数的StreamHandler,也可以看到诸如format之类的默认配置。之后我们 跟踪StreamHandler(因为我们想看到日志输出目的地的配置,而handler就是控制日志流向的,所以我们要跟踪它)的源代码:

  1. class StreamHandler(Handler): 
  2.  
  3. ""
  4.  
  5. A handler class which writes logging records, appropriately formatted, 
  6.  
  7. to a stream. Note that this class does not close the stream, as 
  8.  
  9. sys.stdout or sys.stderr may be used. 
  10.  
  11. ""
  12.  
  13. def __init__(self, stream=None): 
  14.  
  15. ""
  16.  
  17. Initialize the handler. 
  18.  
  19. If stream is not specified, sys.stderr is used. 
  20.  
  21. ""
  22.  
  23. Handler.__init__(self) 
  24.  
  25. if stream is None: 
  26.  
  27. stream = sys.stderr  #### 
  28.  
  29. self.stream = stream 

不带参数的StreamHandler将会把日志流定位到sys.stderr流,标准错误流同样会输出到控制台

知识点2:basicConfig函数用来配置RootLogger

 basicConfig函数仅用来配置RootLogger,rootLogger是所有Logger的祖先Logger,所以其他一切Logger会继承该Logger的配置。

从上面的basicConfig源码看,它可以有六个关键字参数,分别为:

filename:执行使用该文件名为rootLogger创建FileHandler,而不是StreamHandler

filemode:指定文件打开方式,默认是"a"

stream:指定一个流来初始化StreamHandler。此参数不能和filename共存,如果同时提供了这两个参数,则stream参数被忽略

format:为rootLogger的handler指定输出格式

datefmt:指定输出的日期时间格式

level:设置rootLogger的日志级别

使用样例:

  1. logging.basicConfig( 
  2.  
  3.    filename = './log.txt'
  4.  
  5.    filemode = 'a'
  6.  
  7.    #stream = sys.stdout, 
  8.  
  9.    format = '%(levelname)s:%(message)s'
  10.  
  11.    datefmt = '%m/%d/%Y %I:%M:%S'
  12.  
  13.    level = logging.DEBUG 

知识点3 通过示例详细讨论Logger配置的继承关系

首先准备下继承条件:log2继承自log1,logger的名称可以随意,要注意‘.’表示的继承关系。

  1. #coding:utf-8 
  2. import logging 
  3.  
  4. log1 = logging.getLogger("mydear"
  5.  
  6. log1.setLevel(logging.WARNING) 
  7.  
  8. log1.addHandler(StreamHandler()) 
  9.  
  10. log2 = logging.getLogger("mydear.app"
  11.  
  12. log2.error("display"
  13.  
  14. log2.info("not display"

level的继承

原则:子logger写日志时,优先使用本身设置了的level;如果没有设置,则逐层向上级父logger查询,直到查询到为止。最极端的情况是,使用rootLogger的默认日志级别logging.WARNING。

从源代码中看更为清晰, 感谢python的所见即所得:

  1. def getEffectiveLevel(self): 
  2.  
  3. """ 
  4.  
  5. Get the effective level for this logger. 
  6.  
  7. Loop through this logger and its parents in the logger hierarchy, 
  8.  
  9. looking for a non-zero logging level. Return the first one found. 
  10.  
  11. """ 
  12.  
  13. logger = self 
  14.  
  15. while logger: 
  16.  
  17. if logger.level: 
  18.  
  19. return logger.level 
  20.  
  21. logger = logger.parent 
  22.  
  23. return NOTSET 

handler的继承

原则:先将日志对象传递给子logger的所有handler处理,处理完毕后,如果该子logger的propagate属性没有设置为0,则将日志对象向上传递给第一个父Logger,该父logger的所有handler处理完毕后,如果它的propagate也没有设置为0,则继续向上层传递,以此类推。最终的状态,要么遇到一个Logger,它的propagate属性设置为了0;要么一直传递直到rootLogger处理完毕。

在上面实例代码的基础上,我们再添加一句代码,即:

  1. #coding:utf-8 
  2.  
  3. import logging 
  4.  
  5. log1 = logging.getLogger("mydear"
  6.  
  7. log1.setLevel(logging.WARNING) 
  8.  
  9. log1.addHandler(StreamHandler()) 
  10.  
  11. log2 = logging.getLogger("mydear.app"
  12.  
  13. log2.error("display"
  14.  
  15. log2.info("not display"
  16.  
  17. print log2.handlers  #打印log2绑定的handler 

输出如下:

display
[]

说好的继承,但是子logger竟然没有绑定父类的handler,what's wrong?

看到下面调用handler的源代码,就真相大白了。可以理解成,这不是真正的(类)继承,只是"行为上的继承":

  1. def callHandlers(self, record): 
  2.  
  3. ""
  4.  
  5. Pass a record to all relevant handlers. 
  6. Loop through all handlers for this logger and its parents in the 
  7.  
  8. logger hierarchy. If no handler was found, output a one-off error 
  9.  
  10. message to sys.stderr. Stop searching up the hierarchy whenever a 
  11.  
  12. logger with the "propagate" attribute set to zero is found - that 
  13.  
  14. will be the last logger whose handlers are called. 
  15.  
  16. ""
  17.  
  18. c = self 
  19.  
  20. found = 0 
  21.  
  22. while c: 
  23.  
  24. for hdlr in c.handlers: #首先遍历子logger的所有handler 
  25.  
  26. found = found + 1 
  27.  
  28. if record.levelno >= hdlr.level: 
  29.  
  30. hdlr.handle(record) 
  31.  
  32. if not c.propagate: #如果logger的propagate属性设置为0,停止 
  33.  
  34. c = None#break out  
  35.  
  36. else:   #否则使用直接父logger 
  37.  
  38. c = c.parent 
  39.  
  40. ... 

额,最简单的样例牵引出来这么多后台的逻辑,不过我们懂一下也是有好处的。

下面,我们将一些零碎的不是很重要的东西罗列一下,这篇就结束了。

1.几种LogLevel是全局变量,以整数形式表示,也可以但是不推荐自定义日志级别,如果需要将level设置为用户配置,则获取level和检查level的一般代码是:

  1. #假设loglevel代表用户设置的level内容 
  2.  
  3. numeric_level = getattr(logging, loglevel.upper(), None) 
  4.  
  5. if not isinstance(numeric_level, int): 
  6.  
  7. raise ValueError('Invalid log level: %s' % loglevel) 
  8.  
  9. logging.basicConfig(level=numeric_level, ...) 

2.format格式,用于创建formatter对象,或者basicConfig中,就不翻译了

%(name)s    Name of the logger (logging channel)
    %(levelno)s Numeric logging level for the message (DEBUG, INFO,
    WARNING, ERROR, CRITICAL)
    %(levelname)s   Text logging level for the message ("DEBUG", "INFO",
    "WARNING", "ERROR", "CRITICAL")
    %(pathname)s    Full pathname of the source file where the logging
    call was issued (if available)
    %(filename)s    Filename portion of pathname
    %(module)s  Module (name portion of filename)
    %(lineno)d  Source line number where the logging call was issued
    (if available)
    %(funcName)s    Function name
    %(created)f Time when the LogRecord was created (time.time()
    return value)
    %(asctime)s Textual time when the LogRecord was created
    %(msecs)d   Millisecond portion of the creation time
    %(relativeCreated)d Time in milliseconds when the LogRecord was created,
    relative to the time the logging module was loaded
    (typically at application startup time)
    %(thread)d  Thread ID (if available)
    %(threadName)s  Thread name (if available)
    %(process)d Process ID (if available)
    %(message)s The result of record.getMessage(), computed just as
    the record is emitted

3.写日志接口

logging.warn("%s am a hero", "I")   #1 %格式以参数形式提供实参

logging.warn("%s am a hero" % ("I",)) #2 直接提供字符串,也可以使用format,template

logging.warn("%(name)s am a hero", {'name':"I"})  #关键字参数   

logging.warn("%(name)s am a hero" % {'name':"I"}) #甚至这样也可以

logging.warn("%(name)s am a hero, %(value)s" % {'name':"I", 'value':'Yes'}) #原来%也能解析关键字参数,不一定非是元组

如果关键字和位置参数混用呢,%应该不会有什么作为了,最强也就能这样:

logging.warn("%(name)s am a hero, %()s" % {'name':"I" ,'': 'Yes'})#也是字典格式化的原理

4.配置logging:

上 面已经讲了如果配置handler,绑定到logger。如果需要一个稍微庞大的日志系统,可以想象,我们会使用好多的 addHandler,SetFormatter之类的,有够烦了。幸好,logging模块提供了两种额外配置方法,不需要写众多代码,直接从配置结构 中获悉我们的配置意图

方式一:使用配置文件

  1. import logging 
  2.  
  3. import logging.config 
  4.  
  5. logging.config.fileConfig('logging.conf'
  6.  
  7. # create logger 
  8.  
  9. logger = logging.getLogger('simpleExample'
  10.  
  11. # 'application' code 
  12.  
  13. logger.debug('debug message'
  14.  
  15. logger.info('info message'
  16.  
  17. logger.warn('warn message'
  18.  
  19. logger.error('error message'
  20.  
  21. logger.critical('critical message'
  22.  
  23.   
  24.  
  25. #配置文件logging.conf的内容 
  26.  
  27. [loggers] 
  28.  
  29. keys=root,simpleExample 
  30.  
  31. [handlers] 
  32.  
  33. keys=consoleHandler 
  34.  
  35. [formatters] 
  36.  
  37. keys=simpleFormatter 
  38.  
  39. [logger_root] 
  40.  
  41. level=DEBUG 
  42.  
  43. handlers=consoleHandler 
  44.  
  45. [logger_simpleExample] 
  46.  
  47. level=DEBUG 
  48.  
  49. handlers=consoleHandler 
  50.  
  51. qualname=simpleExample 
  52.  
  53. propagate=0 
  54.  
  55. [handler_consoleHandler] 
  56.  
  57. class=StreamHandler 
  58.  
  59. level=DEBUG 
  60.  
  61. formatter=simpleFormatter 
  62.  
  63. args=(sys.stdout,) 
  64.  
  65. [formatter_simpleFormatter] 
  66.  
  67. format=%(asctime)s - %(name)s - %(levelname)s - %(message)s 
  68.  
  69. datefmt= 

方式二:使用字典

请参阅python2.7.9 Library文档,链接:

https://docs.python.org/2/library/logging.config.html?highlight=dictconfig#configuration-dictionary-schema

5.众多的handler满足不同的输出需要

StreamHandler,FileHandler,NullHandler,RotatingFileHandler,TimedRotatingFileHandler,SocketHandler,DatagramHandler,SMTPHandler,SysLogHandler,NTEventLogHandler,MemoryHandler,HTTPHandler,WatchedFileHandler,

其中前三种在logging模块中给出,其他的在logging.handlers模块中给出。

 

 




评论关闭