Python之logging日志

  Python中的logging模块,功能强大,非常方便调试输出。Django也使用了Python自带的logging模块作为日志打印工具。本文简单介绍下logging模块的使用方法,以及如何在Django中使用。

1、简介

  logging模块定义的函数和类为应用程序和库的开发实现了一个灵活的事件日志系统。logging模块是Python的一个标准库模块,由标准库模块提供日志记录API的关键好处是所有Python模块都可以使用这个日志记录功能。

  logging是线程安全的,主要功能由4部分组成:

组件             功能     
Logger 用户使用的直接接口,将日志传递给Handler
Handler 控制日志输出到哪里,console,file… ,一个logger可以有多个Handler
Filter 控制哪些日志可以从logger流向Handler
Formatter 控制日志的格式

2、最简单的使用方法

  最简单的使用方法为“两步走”方式,1)导入模块,2)记录日志内容,多用于输出一个简单的日志内容。此时日志将按照默认格式输出。默认格式为:"%(levelname)s:%(name)s:%(message)s"。如果希望改变日志格式,可以调用basicConfig函数完成。

1
2
3
4
5
6
7
8
# 最简使用方法
import logging

#改变默认日志格式
logging.basicConfig(level=logging.INFO,
format='[%(threadName)-9s(%(lineno)d)] %(message)s')

logging.info('log内容')
  basicConfig函数中level参数控制日志输出级别(即Filter)。logging模块中定义的日志级别有5个,

日志级别(level)          描述            
logging.DEBUG 最详细的日志信息,典型应用场景是 问题诊断
logging.INFO 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作
logging.WARNING 当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的
logging.ERROR 由于一个更严重的问题导致某些功能不能正常运行时记录的信息
logging.CRITICAL 当发生严重错误,导致应用程序不能继续运行时记录的信息

  basicConfig函数所有参数如下表所示:

参数名称                 描述 常用
filename 指定日志输出目标文件的文件名,指定该设置项后日志就不会被输出到控制台了  
filemode 指定日志文件的打开模式,默认为追加模式('a')。该选项在filename指定时才有效  
format 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。
datefmt 指定日期/时间格式。仅在format中包含时间字段%(asctime)s时才有效  
level 指定日志器的日志级别
stream 指定日志输出目标流,多指向控制台。  
style 指定format格式字符串的风格,可取值为'%'、'{'和'$',默认为'%'  
handlers 该选项应是包含多个Handler的可迭代对象,这些Handler将会被添加到root logger  

注意:filenamestreamhandlers这三个参数仅能取其一,不能同时出现,否则会引发ValueError异常。

3、标准使用方法

  在basicConfig函数进行完整参数配置,已经接近标准logging模块的使用方法。logging模块的标准使用方法,包括自定义上述4个组件,控制日志的格式、级别及输出对象等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import logging
import os
from datetime import datetime


class Task:
def __init__(self,logLevel=logging.DEBUG):
log = self.__logFileName()
self.__logger=self.__setupLogger(log,logLevel=logLevel)
self.__logger.info("脚本 {} 开始运行,时间:{} ".format(
os.path.basename(__file__), self.__currentTime()))


def __logFileName(self):
'''
在脚本目录下,创建一个同名的log文件
'''
# 脚本所在路径
scriptPath = os.path.dirname(os.path.abspath(__file__))
# 脚本名(去除扩展名)
scriptName = os.path.splitext(os.path.abspath(__file__))[0]
# 日志文件
logFile = "{}.log".format(scriptPath,scriptName)
logFile = os.path.join(scriptPath, logFile)
return logFile

def __currentTime(self, used_in_filename=False):
# 默认为标准显示时间(used_in_filename为False),否则用于文件名
timeFmt = "%Y%m%d-%H%M%S" if used_in_filename else "%Y-%m-%d %H:%M:%S"
return datetime.now().strftime(timeFmt)

# 创建日志
def __setupLogger(self,logFileName, logLevel=logging.DEBUG):
# 获取logger
logger = logging.getLogger("runLog")
# 设置logger级别
logger.setLevel(logLevel)
# 设置Handlers,这里有两个Handler,一个为文件,一个为控制台
fh = logging.FileHandler(logFileName, 'w', encoding='utf-8')
ch = logging.StreamHandler()
# 设置logger的输出格式
fmtStr = ""
if logLevel == logging.ERROR:
fmtStr = "%(asctime)s (%(filename)s:%(lineno)d)[%(levelname)s]:%(message)s"
elif logLevel == logging.CRITICAL:
fmtStr = "%(asctime)s (%(filename)s:%(lineno)d)[%(levelname)s]:%(message)s"
elif logLevel == logging.DEBUG:
fmtStr = "%(asctime)s (%(filename)s:%(lineno)d)[%(levelname)s]:%(message)s"

logFmt = logging.Formatter(fmtStr, "%Y-%m-%d %H:%M:%S")

# 将格式告诉Handler
fh.setFormatter(logFmt)
ch.setFormatter(logFmt)

# 向logger中添加Handlers
logger.addHandler(fh)
logger.addHandler(ch)

return logger

# 关闭日志
def __closeLogger(self):
for hdl in self.__logger.handlers[:]:
hdl.close()
self.__logger.removeHandler(hdl)

def end(self):
logger = self.__logger
logger.info("脚本 %s 运行完成,时间:%s " %
(os.path.basename(__file__), self.__currentTime()))
self.__closeLogger()

if __name__=="__main__":

task = Task()
# do dask...
# ...
#关闭log
task.end()

logging模块中,Formatter可使用的预定义格式化字符串如下表:

字段/属性名称 使用格式             描述
asctime %(asctime)s 日志事件发生的时间,如:2018-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 进程名称
thread %(thread)d 线程ID
threadName %(thread)s 线程名称

logging模块中,预定义的Handler下表:

Handler         描述
logging.StreamHandler 将日志消息发送到输出到Stream,如std.out, std.err或任何file-like对象。
logging.FileHandler 将日志消息发送到磁盘文件,默认情况下文件大小会无限增长
logging.handlers.RotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按大小切割
logging.hanlders.TimedRotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按时间切割
logging.handlers.HTTPHandler 将日志消息以GET或POST的方式发送给一个HTTP服务器
logging.handlers.SMTPHandler 将日志消息发送给一个指定的email地址
logging.NullHandler 该Handler实例会忽略error messages,通常被想使用logging的library开发者使用来避免'No handlers could be found for logger XXX'信息的出现。

4、Django中日志模块的使用

1)项目里setting.py里配置

Django通过在settings文件中使用LOGGING来定制日志输出(包括定义logger, handler, formatter等)

例如,settings文件中定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
BASE_LOG_DIR = os.path.join(BASE_DIR, "log")
LOGGING = {
'version': 1, # 保留字
'disable_existing_loggers': False, # 禁用已经存在的logger实例
# 日志文件的格式
'formatters': {
# 详细的日志格式
'standard': {
'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
'[%(levelname)s][%(message)s]'
},
# 简单的日志格式
'simple': {
'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
},
# 定义一个特殊的日志格式
'collect': {
'format': '%(message)s'
}
},
# 过滤器
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
# 处理器
'handlers': {
# 在终端打印
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志
'class': 'logging.StreamHandler', #
'formatter': 'simple'
},
# 默认的
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"), # 日志文件
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 3, # 最多备份几个
'formatter': 'standard',
'encoding': 'utf-8',
},
# 专门用来记错误日志
'error': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"), # 日志文件
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 5,
'formatter': 'standard',
'encoding': 'utf-8',
},
# 专门定义一个收集特定信息的日志
'collect': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 5,
'formatter': 'collect',
'encoding': "utf-8"
}
},
'loggers': {
# 默认的logger应用如下配置
'': {
'handlers': ['default', 'console', 'error'], # 上线之后可以把'console'移除
'level': 'DEBUG',
'propagate': True, # 向不向更高级别的logger传递
},
# 名为 'collect'的logger还单独处理
'collect': {
'handlers': ['console', 'collect'],
'level': 'INFO',
}
},
}

2)views.py里使用

1
2
3
4
5
6
7
8
9
10
11
import logging
# 生成一个以当前文件名为名字的logger实例
logger = logging.getLogger(__name__)


def index(request):
logger.debug("调试日志信息....")
logger.info("通知日志信息.....")


return HttpResponse("OK")