python的logging模块(logging是线程安全的)给应用程序提供了标准的日志信息输出接口。logging不仅支持把日志输出到文件,还支持把日志输出到TCP/UDP服务器,EMAIL服务器,HTTP服务器,UNIX的syslog系统等。在logging中主要有四个概念:logger、handler、filter和formatter,下面会分别介绍。
Logger
对象扮演了三重角色:
Logger
对象根据日志的级别或根据Filter
对象,来决定记录哪些日志。
Logger
对象负责把日志信息传递给相关的handler。
在Logger
对象中,最常使用的方法分为两类:configuration,message sending。
configuration方法包括:
message sending方法包括:
debug(log_message, [*args[, **kwargs]])
DEBUG
级别,记录log_message % args
。为了记录异常信息,需要将关键字参数exc_info
设置为一个true值。logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
info(log_message, [*args[, **kwargs]])
INFO
级别,记录log_message % args
。为了记录异常信息,需要将关键字参数exc_info
设置为一个true值。logger.info("Houston, we have a %s", "interesting problem", exc_info=1)
warning(log_message, [*args[, **kwargs]])
WARNING
级别,记录log_message % args
。为了记录异常信息,需要将关键字参数exc_info
设置为一个true值。logger.warning("Houston, we have a %s", "bit of a problem", exc_info=1)
error(log_message, [*args[, **kwargs]])
Error
级别,记录log_message % args
。为了记录异常信息,需要将关键字参数exc_info
设置为一个true值。logger.error("Houston, we have a %s", "major problem", exc_info=1)
critical(log_message, [*args[, **kwargs]])
CRITICAL
级别,记录log_message % args
。为了记录异常信息,需要将关键字参数exc_info
设置为一个true值。logger.critical("Houston, we have a %s", "major disaster", exc_info=1)
exception(message[, *args])
self.error(*((msg,) + args), **{'exc_info': 1})
log(log_level, log_message, [*args[, **kwargs]])
level
,记录log_message % args
。为了记录异常信息,需要将关键字参数exc_info
设置为一个true值。logger.log(level, "We have a %s", "mysterious problem", exc_info=1)
logging.getLogger([name])
方法返回一个Logger
实例的引用,如果提供了name参数,那么它就是这个Logger
实例的名称,如果没提供name参数,那么这个Logger
实例的名称是root。可以通过Logger
实例的name属性,来查看Logger
实例的名称。Logger
实例的名称是使用句号(.)分隔的多级结构。在这种命名方式中,后面的logger是前面的logger的子(父子logger只是简单的通过命名来识别),比如:有一个名称为foo的logger,那么诸如foo.bar、foo.bar.baz和foo.bam这样的logger都是foo这个logger的子logger。子logger会自动继承父logger的定义和配置。
使用相同的名称多次调用logging.getLogger([name])
方法,会返回同一个logger对象的引用。这个规则不仅仅在同一个module有效,而且对在同一个Python解释器进程的多个module也有效。因此应用程序可以在一个module中定义一个父logger,然后在其他module中继承这个logger,而不必把所有的logger都配置一遍。
handler实例负责把日志事件分发到具体的目的地。logger对象可以使用addHandler()
方法,添加零个或多个handler对象到它自身。一个常见的场景是:应用程序可能希望把所有的日志都记录到一个log文件,所有的ERROR及以上级别的日志都记录到stdout,所有的CRITICAL级别的日志都发送到一个email地址。这个场景需要三个独立的handler,每个handler负责把特定级别的日志发送到特定的地方。
下面是logging模块内置的handler:
内置的handler提供了下面的配置方法:
setLevel()
方法,与logger对象的setLevel()
方法一样,也是用于设置一个日志级别,如果日志的级别低于setLevel()方法设置的值,那么handler不会处理它。 应用程序代码不应该直接实例化和使用handler。logging.Handler
是一个定义了所有的handler都应该实现的接口和建立了子类能够使用(或重写)的一些默认行为的基类。
自定义Handler
自定义的handler必须继承自logging.Handler
,且实现下面的方法:
class Handler(Filterer): def emit(self, record): """ Do whatever it takes to actually log the specified logging record. This version is intended to be implemented by subclasses and so raises a NotImplementedError. """ raise NotImplementedError, 'emit must be implemented '\ 'by Handler subclasses' def flush(self): """ Ensure all logging output has been flushed. This version does nothing and is intended to be implemented by subclasses. """ pass def close(self): """ Tidy up any resources used by the handler. This version does removes the handler from an internal list of handlers which is closed when shutdown() is called. Subclasses should ensure that this gets called from overridden close() methods. """ #get the module data lock, as we're updating a shared structure. _acquireLock() try: #unlikely to raise an exception, but you never know... if self in _handlers: del _handlers[self] if self in _handlerList: _handlerList.remove(self) finally: _releaseLock()
其中,emit(record)
方法负责执行真正地记录日志所需的一切事情,在logging.Handler
的子类中必须实现这个方法。close()
方法负责清理handler所使用的资源(在Python解释器退出的时候,会调用所有的handler的flush()
和close()
方法),logging.Handler
的子类应该确保在重写close()
方法的时候,调用父类的该方法。
下面分析logging.StreamHandler
的源代码:
class StreamHandler(Handler): def __init__(self, strm=None): Handler.__init__(self) if strm is None: strm = sys.stderr self.stream = strm def flush(self): if self.stream and hasattr(self.stream, "flush"): self.stream.flush() def emit(self, record): try: msg = self.format(record) stream = self.stream fs = "%s\n" if not hasattr(types, "UnicodeType"): #if no unicode support... stream.write(fs % msg) else: try: if (isinstance(msg, unicode) and getattr(stream, 'encoding', None)): fs = fs.decode(stream.encoding) try: stream.write(fs % msg) except UnicodeEncodeError: #Printing to terminals sometimes fails. For example, #with an encoding of 'cp1251', the above write will #work if written to a stream opened or wrapped by #the codecs module, but fail when writing to a #terminal even when the codepage is set to cp1251. #An extra encoding step seems to be needed. stream.write((fs % msg).encode(stream.encoding)) else: stream.write(fs % msg) except UnicodeError: stream.write(fs % msg.encode("UTF-8")) self.flush() except (KeyboardInterrupt, SystemExit): raise except: self.handleError(record)
在构造函数中,如果提供了strm
参数,那么它就是要输出到的流,如果没提供,那么就会将日志输出到标准错误输出流sys.stderr
。
flush()
方法的作用是:刷新self.stream
内部的I/O缓冲区。每次emit
日志之后都会调用这个方法,将日志从I/O缓冲区sync到self.stream
。
emit(record)
方法的作用是:将LogRecord
对象(record
)记录到self.stream
。emit(record)
方法首先调用基类logging.Handler
提供的format(record)
方法,该方法会根据设置的Formatter
对象来格式化record
对象,得到要记录的字符串msg
。然后对fs
(fs
其实就是在msg
的尾部增加一个换行'\n'
)进行一系列的编码解码,将它写入到self.stream
。最后再刷新self.stream
。在emit(record)
调用期间发生的异常,应该调用logging.Handler
提供的handleError(record)
方法来处理。
Filter
对象用于对LogRecord
对象执行过滤,logger和handler都可以使用filter来过滤record。下面用一个列子来说明Filter
基类的作用:
如果使用A.B实例化一个filter,那么它允许名称为A.B,A.B.C,A.B.C.D这样的logger记录的日志通过,不允许名称为A.BB,B.A.B这样的logger记录的日志通过。 如果使用空字符串实例化一个filter,那么它允许所有的事件通过。
Filter基类有一个方法叫filter(record)
,它用来决定指定的record(LogRecord对象)是否被记录。如果该方法返回0,则不记录record;返回非0则记录record。
Filterer
(注意:不是Filter
)是logger和handler的基类。它提供了方法来添加和删除filter,并且提供了filter(record)
方法用于过滤record,该方法默认允许record被记录,但是任何filter都可以否决这个默认行为,如果想要丢弃record,filter(record)
方法应该返回0,否则应该返回非0。
Formatter
对象用于把一个LogRecord
对象转换成文本,它定义了日志的格式、结构。与logging.Handler
类不同,应用程序可以直接实例化Formatter
类,如果需要也可以子类化Formatter
,以便定制一些行为。
Formatter
的构造函数接受两个参数:第一个参数是用于日志信息的格式化字符串;第二个参数是用于日期的格式化字符串。第二个参数可选的,默认值是%Y-%m-%d %H:%M:%S
。
日志信息的格式化字符串用%(<dictionary key>)s
风格的字符串做替换。下面是替换字符串和它们所代表的含义:
time.time()
的返回值)
record.getMessage()
的返回结果。
下面是一个简单的例子,它会向标准输出打印日志:
import logging import sys logger = logging.getLogger(__name__) filter = logging.Filter(__name__) formatter = logging.Formatter("%(asctime)s|%(name)-12s|%(message)s", "%F %T") stream_handler = logging.StreamHandler(sys.stdout) stream_handler.addFilter(filter) stream_handler.setLevel(logging.DEBUG) stream_handler.setFormatter(formatter) logger.setLevel(logging.DEBUG) logger.addFilter(filter) logger.addHandler(stream_handler) if __name__ == "__main__": logger.info("info")
运行这个脚本,输出结果是:
2015-12-16 13:52:17|__main__ |info
使用配置文件,配置logging
下面是一个使用配置文件,配置logging的例子:
import logging import logging.config logging.config.fileConfig("logging.conf") if __name__ == "__main__": logger = logging.getLogger("test_logging.sublogger") logger.info("info")
logging.conf
如下:
[loggers] keys = root,logger [handlers] keys = stream_handler [formatters] keys = formatter [logger_root] handlers = stream_handler [logger_logger] handlers = stream_handler level = DEBUG propagate = 1 qualname = test_logging [handler_stream_handler] class = StreamHandler args = (sys.stdout,) formatter = formatter level = DEBUG [formatter_formatter] format = %(asctime)s|%(name)-12s|%(message)s datefmt = %F %T
需要解释的地方有两处:第一个是logger_xxx
section中的propagate
选项,在logger对象把record传递给所有相关的handler的时候,会(逐级向上)寻找这个logger和它所有的父logger的全部handler。在寻找过程中,如果logger对象的propagate
属性被设置为1,那么就继续向上寻找;如果某个logger的propagate
属性被设置为0,那么就会停止搜寻。
第二个是logger_xxx
section中的qualname
选项,它其实就是logger的名称。
使用配置文件的时候,必须定义root
logger。
最酷的listen(port)
函数
logging.config.listen(port)
函数可以让应用程序在一个socket上监听新的配置信息,达到在运行时改变配置,而不用重启应用程序的目的。
监听程序:
import logging.config import logging import time logging.config.fileConfig("logging.conf") logger = logging.getLogger("test_logging.listen") t = logging.config.listen(9999) t.setDaemon(True) t.start() try: while True: logger.info('running.') time.sleep(3) except (KeyboardInterrupt, SystemExit, Exception): logging.config.stopListening()
发送新的配置信息程序:
import socket import struct HOST = 'localhost' PORT = 9999 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) print "connected..." data_to_send = open("logging.conf").read() s.send(struct.pack(">L", len(data_to_send))) s.send(data_to_send) print "closing..." s.close()