python之logging模块

网上已经有很多logging模块的资料,在这里只是把看到的几片博客总结一下,也是为了符合自己阅读习惯。

参考的博客地址:

      http://www.cnblogs.com/dahu-daqing/p/7040764.html

      https://www.cnblogs.com/louis-w/p/8567434.html

      https://www.cnblogs.com/testdjt/p/7834856.html

具体logging模块的具体作用,在上面两片博客里面都很清楚。

logging模块的框架:

  •   Loggers: 可供程序直接调用的接口,app通过调用提供的api来记录日志
  •        Handlers: 决定将日志记录分配至正确的目的地
  •        Filters:对日志信息进行过滤, 提供更细粒度的日志是否输出的判断
  •        Formatters: 自定义最终记录打印的格式布局

loggers:

loggers 就是程序可以直接调用的一个日志接口,可以直接向logger写入日志信息。logger并不是直接实例化使用的,而是通过logging.getLogger(name)来获取对象,事实上logger对象是单例模式,logging是多线程安全的,也就是无论程序中哪里需要打日志获取到的logger对象都是同一个。但是不幸的是logger并不支持多进程,这个在后面的章节再解释,并给出一些解决方案。

【注意】loggers对象是有父子关系的,当没有父logger对象时它的父对象是root,当拥有父对象时父子关系会被修正。举个例子,logging.getLogger("abc.xyz") 会创建两个logger对象,一个是abc父对象,一个是xyz子对象,同时abc没有父对象,所以它的父对象是root。但是实际上abc是一个占位对象(虚的日志对象),可以没有handler来处理日志。但是root不是占位对象,如果某一个日志对象打日志时,它的父对象会同时收到日志,所以有些使用者发现创建了一个logger对象时会打两遍日志,就是因为他创建的logger打了一遍日志,同时root对象也打了一遍日志。

handlers:

Handlers 将logger发过来的信息进行准确地分配,送往正确的地方。举个栗子,送往控制台或者文件或者both或者其他地方(进程管道之类的)。它决定了每个日志的行为,是之后需要配置的重点区域。

每个Handler同样有一个日志级别,一个logger可以拥有多个handler也就是说logger可以根据不同的日志级别将日志传递给不同的handler。当然也可以相同的级别传递给多个handlers这就根据需求来灵活的设置了。

logging中包含的handler主要有以下几种:

handler名称:位置;作用

StreamHandler:logging.StreamHandler;日志输出到流,可以是sys.stderr,sys.stdout或者文件
FileHandler:logging.FileHandler;日志输出到文件
BaseRotatingHandler:logging.handlers.BaseRotatingHandler;基本的日志回滚方式
RotatingHandler:logging.handlers.RotatingHandler;日志回滚方式,支持日志文件最大数量和日志文件回滚
TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日志回滚方式,在一定时间区域内回滚日志文件
SocketHandler:logging.handlers.SocketHandler;远程输出日志到TCP/IP sockets
DatagramHandler:logging.handlers.DatagramHandler;远程输出日志到UDP sockets
SMTPHandler:logging.handlers.SMTPHandler;远程输出日志到邮件地址
SysLogHandler:logging.handlers.SysLogHandler;日志输出到syslog
NTEventLogHandler:logging.handlers.NTEventLogHandler;远程输出日志到Windows NT/2000/XP的事件日志
MemoryHandler:logging.handlers.MemoryHandler;日志输出到内存中的指定buffer
HTTPHandler:logging.handlers.HTTPHandler;通过"GET"或者"POST"远程输出到HTTP服务器

 

Filters:

Filters 提供了更细粒度的判断,来决定日志是否需要打印。原则上handler获得一个日志就必定会根据级别被统一处理,但是如果handler拥有一个Filter可以对日志进行额外的处理和判断。例如Filter能够对来自特定源的日志进行拦截or修改甚至修改其日志级别(修改后再进行级别判断)。

logger和handler都可以安装filter甚至可以安装多个filter串联起来。

formatters:

Formatters 指定了最终某条记录打印的格式布局。Formatter会将传递来的信息拼接成一条具体的字符串,默认情况下Format只会将信息%(message)s直接打印出来。Format中有一些自带的LogRecord属性可以使用,如下表格:

asctime             %(asctime)s         日志事件发生的时间--人类可读时间,如:2003-07-08 16:49:45,896
created             %(created)f        日志事件发生的时间--时间戳,就是当时调用time.time()函数返回的值
relativeCreated     %(relativeCreated)d 日志事件发生的时间相对于logging模块加载时间的相对毫秒数(目前还不知道干嘛用的)
msecs               %(msecs)d           日志事件发生事件的毫秒部分
levelname           %(levelname)s       该日志记录的文字形式的日志级别('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
levelno             %(levelno)s         该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)
name                %(name)s            所使用的日志器名称,默认是'root',因为默认使用的是 rootLogger
message             %(message)s         日志记录的文本内容,通过 msg % args计算得到的
pathname            %(pathname)s        调用日志记录函数的源码文件的全路径
filename            %(filename)s        pathname的文件名部分,包含文件后缀
module              %(module)s          filename的名称部分,不包含后缀
lineno              %(lineno)d          调用日志记录函数的源代码所在的行号
funcName            %(funcName)s        调用日志记录函数的函数名
process             %(process)d         进程ID
processName         %(processName)s     进程名称,Python 3.1新增
thread              %(thread)d          线程ID
threadName          %(thread)s          线程名称

一个Handler只能拥有一个Formatter 因此如果要实现多种格式的输出只能用多个Handler来实现。

在formatters中levelname中可以设置日志的级别,日志的级别有以下几种,如下:

日志等级:使用范围

FATAL:致命错误
CRITICAL:特别糟糕的事情,如内存耗尽、磁盘空间为空,一般很少使用
ERROR:发生错误时,如IO操作失败或者连接问题
WARNING:发生很重要的事件,但是并不是错误时,如用户登录密码错误
INFO:处理请求或者状态变化等日常事务
DEBUG:调试过程中使用DEBUG等级,如算法中每个循环的中间状态

低于设置日志级别的信息,将会被忽略不会被记录!

logging中的两个方法:

  • logging.basicConfig(**kwargs)  
    • 为日志模块配置基本信息。kwargs 支持如下几个关键字参数:
      filename :日志文件的保存路径。如果配置了些参数,将自动创建一个FileHandler作为Handler;
      filemode :日志文件的打开模式。 默认值为’a’,表示日志消息以追加的形式添加到日志文件中。如果设为’w’, 那么每次程序启动的时候都会创建一个新的日志文件;
      format :设置日志输出格式;
      datefmt :定义日期格式;
      level :设置日志的级别.对低于该级别的日志消息将被忽略;
      stream :设置特定的流用于初始化StreamHandler;
  • logging.getLogger([name])

    • 创建Logger对象。日志记录的工作主要由Logger对象来完成。在调用getLogger时要提供Logger的名称(注:多次使用相同名称来调用getLogger,返回的是同一个对象的引用。),Logger实例之间有层次关系,这些关系通过Logger名称来体现,如:

      p = logging.getLogger(“root”)

      c1 = logging.getLogger(“root.c1”)

      c2 = logging.getLogger(“root.c2”)

      例子中,p是父logger, c1,c2分别是p的子logger。c1, c2将继承p的设置。如果省略了name参数, getLogger将返回日志对象层次关系中的根Logger。

 

基本使用

简单的直接在控制台输出:

In [6]: logging.info("this is  info message!")      

In [7]: logging.warning("This is  warnings messages")
WARNING:root:This is  warnings messages

logging中默认的日志级别是warning,因此info信息不会打印,只有在warning级别以上的信息才会打印。

上述的日志信息为默认的格式信息,下面使用basicconfig对日志信息进行设置。

In [1]: import logging

In [2]: logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(name)s - %(process)d - %(message)s")

In [3]: logging.debug("this is debug meg")
2018-08-04 14:19:50,113 - DEBUG - root - 25188 - this is debug meg

In [4]: logging.info("this is info meg")
2018-08-04 14:20:03,873 - INFO - root - 25188 - this is info meg

In [5]: logging.warning("this is warning  meg")
2018-08-04 14:20:25,041 - WARNING - root - 25188 - this is warning  meg

上述的日志信息,按照自定义的日志格式输出。

日志输出到文本:

#!/usr/bin/env python
#*-* coding:utf-8 *-*
import logging

LOG_FORMAT = "%(asctime)s - %(name)s - %(process)d - %(message)s"
DATE_FORMAT = "%Y-%m-%d %H:%M:%S %P"

logging.basicConfig(filename="log.txt", level=logging.ERROR, format=LOG_FORMAT, datefmt=DATE_FORMAT)

logging.debug("This is a debug log.")
logging.error("this is a error")
logging.critical("This is a critical log.")

=====执行结果=====
[root@os1 workgit]# python log.py    #控制台并不会输出任何结果
[root@os1 workgit]# cat log.txt      #文本输出
2018-08-04 15:20:29 pm - root - 30511 - this is a error
2018-08-04 15:20:29 pm - root - 30511 - This is a critical log.
[root@os1 workgit]#

 

日志输出到控制台和文本

cat log.py
#!/usr/bin/env python
#*-* coding:utf-8 *-*
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')


handler = logging.FileHandler("log.txt")
handler.setLevel(logging.DEBUG)   #这里设置了日志级别
handler.setFormatter(formatter)

console = logging.StreamHandler() #初始化handler
console.setLevel(logging.INFO)    #设置日志的级别
console.setFormatter(formatter)   #设置日志格式

logger.addHandler(handler)
logger.addHandler(console)


logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")

#整个过程就是先初始化一个logger,然后初始化handler,最后把handler添加进logger!

执行结果:

[root@os1 workgit]# python log.py
2018-08-04 15:08:12,266 - __main__ - INFO - Start print log
2018-08-04 15:08:12,267 - __main__ - WARNING - Something maybe fail.
2018-08-04 15:08:12,267 - __main__ - INFO - Finish

#文本结果
[root@os1 workgit]# cat log.txt 
2018-08-04 15:08:12,266 - __main__ - INFO - Start print log
2018-08-04 15:08:12,267 - __main__ - WARNING - Something maybe fail.
2018-08-04 15:08:12,267 - __main__ - INFO - Finish

【疑问】
在filehandler中设置了日志的默认级别为debug,但是文件中的调试信息并没有打出,handler的setlevel设置与logger的setlevel设置究竟是怎么样生效的?
未解决!!!!!!!!!!!!

上面的设置已经实现了在控制台和日志中打印输出日志的功能!

几点说明

  • logging.basicConfig()函数是一个一次性的简单配置工具使,也就是说只有在第一次调用该函数时会起作用,后续再次调用该函数时完全不会产生任何操作的,多次调用的设置并不是累加操作。
  • 日志器(Logger)是有层级关系的,上面调用的logging模块级别的函数所使用的日志器是RootLogger类的实例,其名称为'root',它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。
  • 如果要记录的日志中包含变量数据,可使用一个格式字符串作为这个事件的描述消息(logging.debug、logging.info等函数的第一个参数),然后将变量数据作为第二个参数*args的值进行传递,如:logging.warning('%s is %d years old.', 'Tom', 10),输出内容为WARNING:root:Tom is 10 years old.
  • logging.debug(), logging.info()等方法的定义中,除了msg和args参数外,还有一个**kwargs参数。它们支持3个关键字参数: exc_info, stack_info, extra,下面对这几个关键字参数作个说明。
关于exc_info, stack_info, extra关键词参数的说明:
  • exc_info: 其值为布尔值,如果该参数的值设置为True,则会将异常异常信息添加到日志消息中。如果没有异常信息则添加None到日志信息中。
  • stack_info: 其值也为布尔值,默认值为False。如果该参数的值设置为True,栈信息将会被添加到日志信息中。
  • extra: 这是一个字典(dict)参数,它可以用来自定义消息格式中所包含的字段,但是它的key不能与logging模块定义的字段冲突。

 

这些字段介绍,参考上面的第三个博文。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

在博文开始的时候,说明了logging模块的四个核心组件,这个四个核心组件彼此之间是怎么工作的以及各自又是怎么工作的?

四个组件的描述:

  • 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
  • 不同的处理器(handler)可以将日志输出到不同的位置;
  • 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
  • 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
  • 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。

简单点说就是:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。

具体模块的功能介绍,推荐上面博文的第三篇:https://www.cnblogs.com/testdjt/p/7834856.html

捕捉异常

在python中使用traceback追踪一些错误的信息,这里使用logging.error!

#!/usr/bin/env python
#*-* coding:utf-8 *-*
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

console = logging.StreamHandler()
console.setLevel(logging.INFO)

logger.addHandler(handler)
logger.addHandler(console)

logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
try:
    open("sklearn.txt","rb")
except (SystemExit,KeyboardInterrupt):
    raise
except Exception:
    logger.error("Faild to open sklearn.txt from logger.error",exc_info = True)
    #logger.error("Faild to open sklearn.txt from logger.error")

logger.info("Finish")

#通过注释可以查看exc_info = True参数输出的信息

输出的结果如下:

-----------注释前----------------
[root@os1 workgit]# python log.py
Start print log
Something maybe fail.
Faild to open sklearn.txt from logger.error
Traceback (most recent call last):
  File "log.py", line 21, in <module>
    open("sklearn.txt","rb")
IOError: [Errno 2] No such file or directory: 'sklearn.txt'
Finish
-----------注释后-----------------
[root@os1 workgit]# python log.py
Start print log
Something maybe fail.
Faild to open sklearn.txt from logger.error
Finish

----------注释前日志文件中的结果---
[root@os1 workgit]# cat log.txt 
2018-08-04 15:47:55,656 - __main__ - INFO - Start print log
2018-08-04 15:47:55,656 - __main__ - WARNING - Something maybe fail.
2018-08-04 15:47:55,656 - __main__ - ERROR - Faild to open sklearn.txt from logger.error
Traceback (most recent call last):
  File "log.py", line 21, in <module>
    open("sklearn.txt","rb")
IOError: [Errno 2] No such file or directory: 'sklearn.txt'
2018-08-04 15:47:55,656 - __main__ - INFO - Finish

 

posted @ 2018-08-04 16:58  夜间独行的浪子  阅读(223)  评论(0编辑  收藏  举报