Log(日志的记录)
1、日志的作用
如果我们现在只能得知当前问题的现象,而没有其他任何信息的话,如果我们想要解决掉这个问题的话,那么只能根据问题的现象来试图复现一下,然后再一步步去调试,这恐怕是很难的,很大的概率上我们是无法精准地复现这个问题的,而且 Debug 的过程也会耗费巨多的时间,这样一旦生产环境上出现了问题,修复就会变得非常棘手。但这如果我们当时有做日志记录的话,不论是正常运行还是出现报错,都有相关的时间记录,状态记录,错误记录等,那么这样我们就可以方便地追踪到在当时的运行过程中出现了怎样的状况,从而可以快速排查问题。
因此,日志记录是非常有必要的,任何一款软件如果没有标准的日志记录
2、日志的等级
日志等级(level) | 描述 |
---|---|
DEBUG | 最详细的日志信息,典型应用场景是 问题诊断 |
INFO | 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作 |
WARNING | 当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的 |
ERROR | 由于一个更严重的问题导致某些功能不能正常运行时记录的信息 |
CRITICAL | 当发生严重错误,导致应用程序不能继续运行时记录的信息 |
2、1 logging四大模块
组件 | 说明 |
---|---|
loggers | 提供应用程序代码直接使用的接口 |
handlers | 用于将日志记录发送到指定的目的位置 |
filters | 提供更细粒度的日志过滤功能,用于决定哪些日志记录将会被输出(其它的日志记录将会被忽略) |
formatters | 用于控制日志信息的最终输出格式 |
import logging
8
9 logging.debug("debug message") #告警级别最低,只有在诊断问题时才有兴趣的详细信息。
10
11 logging.info("info message") #告警级别比debug要高,确认事情按预期进行。
12
13 logging.warning("warning message") #告警级别比info要高,该模式是默认的告警级别!预示着一些意想不到的事情发生,或在不久的将来出现一些问题(例如“磁盘空间低”)。该软件仍在正常工作。
14
15 logging.error("error message") #告警级别要比warning药膏,由于一个更严重的问题,该软件还不能执行某些功能。
16
17 logging.critical("critical message") #告警级别要比error还要高,严重错误,表明程序本身可能无法继续运行。
18
19
20
21
22#因为logging默认等级为warning 如果低于这个等级日志不会被记录
23 #以上代码执行结果如下:
24 WARNING:root:warning message
25 ERROR:root:error message
26 CRITICAL:root:critical message
3、logging.basicConfig()
函数说明
该方法用于为logging日志系统做一些基本配置(设置去全局)
参数名称 | 描述 |
---|---|
filename | 指定日志输出目标文件的文件名,指定该设置项后日志信心就不会被输出到控制台了 |
filemode | 指定日志文件的打开模式,默认为'a'。(也可以是w 不建议)需要注意的是,该选项要在filename指定时才有效 |
format | 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。 |
datefmt | 指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效 |
level | 指定日志器的日志级别 |
stream | 指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 (一般不用)ValueError 异常 |
style | Python 3.2中新添加的配置项。指定format格式字符串的风格,可取值为'%'、'{'和'$',默认为'%' |
handlers | Python 3.3中新添加的配置项。该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常。(一般不用) |
3、1format参数
format:指定日志信息的输出格式,即上文示例所示的参数,详细参数可以参考:docs.python.org/3/library/l…,部分参数如下所示:
%(levelno)s
:打印日志级别的数值。
%(levelname)s
:打印日志级别的名称。
%(pathname)s
:打印当前执行程序的路径,其实就是sys.argv[0]。
%(filename)s
:打印当前执行程序名。
%(funcName)s
:打印日志的当前函数。
%(lineno)d
:打印日志的当前行号。
%(asctime)s
:打印日志的时间。
%(thread)d
:打印线程ID。
%(threadName)s
:打印线程名称。
%(process)d
:打印进程ID。
%(processName)s
:打印线程名称。
%(module)s
:打印模块名称。
%(message)s
:打印日志信息。
4、自我定制log输出形式
4、1设置level
等级 | 数值 |
---|---|
CRITICAL | 50 |
FATAL | 50 |
ERROR | 40 |
WARNING | 30 |
WARN | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
import logging
logger = logging.getLogger(__name__)#像是格式一样
logger.setLevel(level=logging.WARN)#设置日志的显示等级
# Log
logger.debug('Debugging')
logger.critical('Critical Something')
logger.error('Error Occurred')
logger.warning('Warning exists')
logger.info('Finished')
这里我们设置了输出级别为 WARN
,然后对应输出了五种不同级别的日志信息,运行结果如下:
Critical Something
Error Occurred
Warning exists
设置logging
每个程序在输出信息之前都要获得一个Logger。Logger通常对应了程序的模块名.
1>.比如聊天工具的图形界面模块可以这样获得它的Logger: LOG=logging.getLogger(”chat.gui”)
2>.而核心模块可以这样:LOG=logging.getLogger(”chat.kernel”)
3>.指定最低的日志级别,低于lel的级别将被忽略。debug是最低的内置级别,critical为最高
Logger.setLevel(lel)
4>.添加或删除指定的filter
Logger.addFilter(filt)、Logger.removeFilter(filt)
5>.增加或删除指定的handler
Logger.addHandler(hdlr)、Logger.removeHandler(hdlr)
6>.可以设置的日志级别
Logger.debug()、Logger.info()、Logger.warning()、Logger.error()、Logger.critical()
4、2 Handler的设置
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)#设置等级
handler = logging.FileHandler('output.log')#文件路径和文件名
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')#设置日志输出格式
handler.setFormatter(formatter)#把formatter添加到handler
logger.addHandler(handler)#添加到handler
#输出日志
logger.info('This is a log info')
logger.debug('Debugging')
logger.warning('Warning exists')
logger.info('Finish')
这里我们没有再使用 basicConfig 全局配置,而是先声明了一个 Logger 对象,然后指定了其对应的 Handler 为 FileHandler
对象,然后 Handler 对象还单独指定了 Formatter 对象单独配置输出格式,最后给 Logger 对象添加对应的 Handler 即可,最后可以发现日志就会被输出到 output.log
中,内容如下:
2018-06-03 14:53:36,467 - __main__ - INFO - This is a log info
2018-06-03 14:53:36,468 - __main__ - WARNING - Warning exists
2018-06-03 14:53:36,468 - __main__ - INFO - Finish
另外我们还可以使用其他的 Handler 进行日志的输出,logging 模块提供的 Handler 有
StreamHandler或logging.StreamHandler; | 将日志消息发送到输出到Stream,如std.out, std.err或任何file-like对象。 |
---|---|
FileHandler:logging.FileHandler | 日志输出到文件。 |
BaseRotatingHandler或logging.handlers.BaseRotatingHandler | 基本的日志回滚方式。 |
RotatingHandler或logging.handlers.RotatingHandler | 日志回滚方式,支持日志文件最大数量和日志文件回滚。并支持日志文件按大小切割 |
TimeRotatingHandler或logging.handlers.TimeRotatingHandler | 日志回滚方式,在一定时间区域内回滚日志文件。 |
SocketHandler或logging.handlers.SocketHandler | 远程输出日志到TCP/IP sockets |
SMTPHandler或logging.handlers.SMTPHandler | 远程输出日志到邮件地址 |
HTTPHandler或logging.handlers.HTTPHandler | 通过”GET”或者”POST”远程输出到HTTP服务器 |
handler
handler
对象负责发送相关的信息到指定目的地。Python的日志系统有多种Handler可以使用。有些Handler可以把信息输出到控制台,有些Logger可以把信息输出到文件,还有些
Handler可以把信息发送到网络上。如果觉得不够用,还可以编写自己的Handler。可以通过addHandler()方法添加多个多handler
#1>.指定被处理的信息级别,低于lel级别的信息将被忽略Handler.setLevel(lel)
#2>.给这个handler选择一个格式Handler.setFormatter(
)
#3>.新增或删除一个filter对象Handler.addFilter(filt)、Handler.removeFilter(filt)
每个Logger可以附加多个Handler。接下来我们就来介绍一些常用的Handler:
1>.logging.StreamHandler
使用这个Handler可以向类似与sys.stdout或者sys.stderr的任何文件对象(file object)输出信息。它的构造函数是:StreamHandler([strm]),其中strm参数是一个文件对象。默认是sys.stderr(控制台)
2>.logging.FileHandler
和StreamHandler类似,用于向一个文件输出日志信息。不过FileHandler
会帮你打开这个文件。它的构造函数是:FileHandler(filename[,mode]),filename)
是文件名,必须指定一个文件名。mode是文件的打开方式。参见Python内置函数open()的用法。默认是’a’,即添加到文件末尾。
3>.logging.handlers.RotatingFileHandler
这个Handler类似于上面的FileHandler
,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建
一个新的同名日志文件继续输出。比如日志文件是chat.log。当chat.log达到指定的大小之后,RotatingFileHandler自动把
文件改名为chat.log.1。不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建
chat.log,继续输出日志信息。它的构造函数是:RotatingFileHandler(filename[, mode[, maxBytes[, backupCount]]])
,其中filename和mode两个参数和FileHandler一样。maxBytes用于指定日志文件的最大文件大小。如果maxBytes为0,
意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。backupCount
用于指定保留的备份文件的个数。比如,如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。
4>.logging.handlers.TimedRotatingFileHandler
这个Handler和RotatingFileHandler类似,不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就
自动创建新的日志文件。重命名的过程与RotatingFileHandler类似,不过新的文件不是附加数字,而是当前时间。它的构造函数是:TimedRotatingFileHandler(filename [,when [,interval [,backupCount]]])
,其中filename
参数和backupCount
参数和RotatingFileHandler具有相同的意义,interva
l是时间间隔。when
参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:(S[秒],M[分],H[小时],D[天],W[每星期(interval==0时代表星期一)],midnight[每天凌晨]
例题
下面我们使用三个 Handler 来实现日志同时输出到控制台、文件、HTTP 服务器:
import logging
from logging.handlers import HTTPHandler
import sys
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)
# StreamHandler 控制台
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(level=logging.DEBUG)
logger.addHandler(stream_handler)
# FileHandler 保存到文件
file_handler = logging.FileHandler('output.log')
file_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# HTTPHandler 发送http
http_handler = HTTPHandler(host='localhost:8001', url='log', method='POST')
logger.addHandler(http_handler)
# Log
logger.info('This is a log info')
logger.debug('Debugging')
logger.warning('Warning exists')
logger.info('Finish')
控制台中
This is a log info
Debugging
Warning exists
Finish
文件中
2018-06-03 15:13:44,895 - __main__ - INFO - This is a log info
2018-06-03 15:13:44,947 - __main__ - WARNING - Warning exists
2018-06-03 15:13:44,949 - __main__ - INFO - Finish
4、3 Formatter
在进行日志格式化输出的时候,我们可以不借助于 basicConfig 来全局配置格式化输出内容,可以借助于 Formatter 来完成,下面我们再来单独看下 Formatter 的用法:(在上边已经用过,和format差不多)
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.WARN)
formatter = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y/%m/%d %H:%M:%S')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
# Log
logger.debug('Debugging')
logger.critical('Critical Something')
logger.error('Error Occurred')
logger.warning('Warning exists')
logger.info('Finished')
在这里我们指定了一个 Formatter
,并传入了 fmt
和 datefmt
参数,这样就指定了日志结果的输出格式和时间格式,然后 handler 通过 setFormatter()
方法设置此 Formatter 对象即可,输出结果如下:
2018/06/03 15:47:15 - __main__ - CRITICAL - Critical Something
2018/06/03 15:47:15 - __main__ - ERROR - Error Occurred
2018/06/03 15:47:15 - __main__ - WARNING - Warning exists
4、4 捕获 Traceback、(具体报错信息)
如果遇到错误,我们更希望报错时出现的详细 Traceback 信息,便于调试,利用 logging 模块我们可以非常方便地实现这个记录,我们用一个实例来感受一下:
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.DEBUG)
# Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# FileHandler
file_handler = logging.FileHandler('result.log')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# StreamHandler
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
# Log
logger.info('Start')
logger.warning('Something maybe fail.')
try:
result = 10 / 0
except Exception:
logger.error('Faild to get result', exc_info=True)
logger.info('Finished')
这里我们在 error()
方法中添加了一个参数,将 exc_info
设置为了 True
,这样我们就可以输出执行过程中的信息了,即完整的 Traceback
信息
2018-06-03 16:00:15,382 - __main__ - INFO - Start print log
2018-06-03 16:00:15,382 - __main__ - DEBUG - Do something
2018-06-03 16:00:15,382 - __main__ - WARNING - Something maybe fail.
2018-06-03 16:00:15,382 - __main__ - ERROR - Faild to get result
Traceback (most recent call last):
File "/private/var/books/aicodes/loggingtest/demo8.py", line 23, in <module>
result = 10 / 0
ZeroDivisionError: division by zero
2018-06-03 16:00:15,383 - __main__ - INFO - Finished
4.5 配置共享
在写项目的时候,我们肯定会将许多配置放置在许多模块下面,这时如果我们每个文件都来配置 logging 配置那就太繁琐了,logging 模块提供了父子模块共享配置的机制,会根据 Logger 的名称来自动加载父模块的配置。
例如我们这里首先定义一个 main.py 文件:
import logging
import core
logger = logging.getLogger('main')
logger.setLevel(level=logging.DEBUG)
# Handler
handler = logging.FileHandler('result.log')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.info('Main Info')
logger.debug('Main Debug')
logger.error('Main Error')
core.run()
我们定义一个core.pywe你那
import logging
logger = logging.getLogger('main.core')
def run():
logger.info('Core Info')
logger.debug('Core Debug')
logger.error('Core Error')
这里我们定义了 Logger 的名称为 main.core,注意这里开头是 main,即刚才我们在 main.py 里面的 Logger 的名称,这样 core.py 里面的 Logger 就会复用 main.py 里面的 Logger 配置,而不用再去配置一次了。
4、6 文件配置(推荐)
在开发过程中,将配置在代码里面写死并不是一个好的习惯,更好的做法是将配置写在配置文件里面,我们可以将配置写入到配置文件,然后运行时读取配置文件里面的配置,这样是更方便管理和维护的,下面我们以一个实例来说明一下,首先我们定义一个 yaml 配置文件:
version: 1
formatters:
brief:
format: "%(asctime)s - %(message)s"
simple:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
console:
class : logging.StreamHandler
formatter: brief
level : INFO
stream : ext://sys.stdout
file:
class : logging.FileHandler
formatter: simple
level: DEBUG
filename: debug.log
error:
class: logging.handlers.RotatingFileHandler
level: ERROR
formatter: simple
filename: error.log
maxBytes: 10485760
backupCount: 20
encoding: utf8
loggers:
main.core:
level: DEBUG
handlers: [console, file, error]
root:
level: DEBUG
handlers: [console]
这里我们定义了 formatters、handlers、loggers、root 等模块,实际上对应的就是各个 Formatter、Handler、Logger 的配置,参数和它们的构造方法都是相同的。
接下来我们定义一个主入口文件,main.py,内容如下:
import logging
import core
import yaml
import logging.config
import os
def setup_logging(default_path='config.yaml', default_level=logging.INFO):
path = default_path
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
config = yaml.load(f)
logging.config.dictConfig(config)
else:
logging.basicConfig(level=default_level)
def log():
logging.debug('Start')
logging.info('Exec')
logging.info('Finished')
if __name__ == '__main__':
yaml_path = 'config.yaml'
setup_logging(yaml_path)
log()
core.run()
另外这个模块还引入了另外一个模块 core,所以我们定义 core.py 如下:
import logging
logger = logging.getLogger('main.core')
def run():
logger.info('Core Info')
logger.debug('Core Debug')
logger.error('Core Error')
观察配置文件,主入口文件 main.py 实际上对应的是 root 一项配置,它指定了 handlers 是 console,即只输出到控制台。另外在 loggers 一项配置里面,我们定义了 main.core 模块,handlers 是 console、file、error 三项,即输出到控制台、输出到普通文件和回滚文件。
这样运行之后,我们便可以看到所有的运行结果输出到了控制台:
2018-06-03 17:07:12,727 - Exec
2018-06-03 17:07:12,727 - Finished
2018-06-03 17:07:12,727 - Core Info
2018-06-03 17:07:12,727 - Core Info
2018-06-03 17:07:12,728 - Core Error
2018-06-03 17:07:12,728 - Core Error
core文件输出
2018-06-03 17:07:12,727 - main.core - INFO - Core Info
2018-06-03 17:07:12,727 - main.core - DEBUG - Core Debug
2018-06-03 17:07:12,728 - main.core - ERROR - Core Error