一、为什么使用日志 logging
通过记录日志,可以了解系统、程序的运行情况;某些情况下,日志还可以记录用户的操作行为、类型爱好等可以用来分析。日志可以在程序发现问题后让开发人员或运维人员快速定位问题所在。总而言是,日志在开发调试、定位故障、了解程序运行情况等方面发挥重要作用。
二、日志输出形式
- 输出到控制台,适合开发阶段
import logging
logging.info('it is start !')
logging.warning('it is run ?')
运行以上程序你只会看到
WARNING:root:it is run ?
这是因为 python 默认的输出日志等级为 WARNING
, 而 INFO
的等级低于WARNING
,被过滤不进行输出。
- 写入磁盘文件,适合生产阶段
import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('this is debug ')
logging.info('this is info')
logging.warning('this is warning')
由于设置了level=logging.DEBUG
,所以三条日志消息都会写入日志文件中,filename='example.log'
定义了日志文件位置,这里使用了相对路径。
三、什么时候使用日志
print 比较适合命令行测试或者一下代码片段,而当你准备开发一个程序或者完整的项目的时候就应该使用日志。
你想要执行的任务 | 此任务的最好的工具 |
---|---|
对于命令行或程序的应用,结果显示在控制台 | print() |
在对程序的普通操作发生时提交事件报告(比如:状态监控和错误调查) |
logging.info() 函数 |
有诊断目的,需要详细输出信息时使用 |
logging.debug() 函数 |
提出一个警告信息基于一个特殊的运行时事件 |
warnings.warn() 位于代码库中,该事件是可以避免的,需要修改客户端应用以消除告警;logging.warning() 不需要修改客户端应用,但是该事件还是需要引起关注 |
报告错误而不引发异常(如在长时间运行中的服务端进程的错误处理) |
logging.error() , logging.exception() 或 logging.critical() 分别适用于特定的错误及应用领域 |
四、日志等级
日志功能应以所追踪事件级别或严重性而定。各级别适用性如下(以严重性递增):
级别 | 何时使用 |
---|---|
DEBUG | 细节信息,仅当诊断问题时适用。 |
INFO | 确认程序按预期运行 |
WARNING | 表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行 |
ERROR | 由于严重的问题,程序的某些功能已经不能正常执行 |
CRITICAL | 严重的错误,表明程序已不能继续执行 |
默认的级别是WARNING
,意味着只会追踪该级别及以上的事件,除非更改日志配置。
五、对日志进行设置 basicConfig()
使用logging.basicConfig(**kwargs)
可以对日志进行设置,如上设置日志的输出模式、日志记录等级。有以下可选参数:
格式 | 描述 |
---|---|
filename | 指定使用指定的文件名而不是StreamHandler,将创建FileHandler,使日志输出到文件。 |
filemod | 如果指定了filename,则在此处指定 open 打开文件的读写模式。默认为'a' ,也就是在文件末尾追加。(一般使用默认的'a' 就可以) |
format | 使用指定的格式字符串作为处理程序。 |
datefmt | 使用指定的日期/时间格式 |
style | 指定格式化字符串的风格,可使用'%' 、'{' 或'$' ,默认使用'%' 。 |
level | 指定日志记录的(最低)级别 |
stream | 使用指定的流初始化StreamHandler。请注意,此参数与filename不兼容- 如果两者都存在,则引发ValueError。 |
handlers | 如果指定,则被添加到根记录器。任何没有格式化的处理程序都将被分配在此函数中创建的默认格式化程序。请注意,此参数与filename或stream不兼容- 如果两者都存在,则引发ValueError。 |
详解
1)filename,如果指定 filename ,则认为是将日志输出到磁盘文件(还可以使用 stream 将日志输入到流
,使用流处理,这里不介绍 流处理 相关内容;两者不兼容,一个日志处理器只能采用一种模式)
2)filemod,指定 open 打开文件的读写模式。一般使用默认的追加模式filemod = 'a'
,这时候对程序运行3次,则日志会累计3次(输出到指定文件中);如果使用filemod = 'w'
,则打开日志发现只记录了一次,这是因为后续的日志会覆盖前面的日志。
3)format,格式日志字符串
# test.py
import logging
logging.basicConfig(level=logging.DEBUG,
format = '%(asctime)s - %(levelname)s - %(message)s - %(lineno)d'
)
logging.info('ok !')
你将会看到打印以下内容
>>>2019-08-05 10:22:40,817 - INFO - ok ! - 6
其中 2019-08-05 10:22:40,817
是我们设置的 %(asctime)s,也就是格式化时间
INFO
是 %(levelname)s,日志等级
ok !
是 %(message)s,logging 输出信息
6
是%(lineno)d , 日志输出所在行。
-
是我们自己设置的分割符,可以根据自己的习惯设定,例如
format = '[%(asctime)s] [%(filename)s] [%(message)s] [%(lineno)d]'
这里介绍一些常用的格式化选项,更多可以看 python 日志模块
格式 | 描述 |
---|---|
%(asctime)s | 格式化时间(例:2019-08-05 10:36:08,504) |
%(created)f | 时间戳(例:1564972568.504417) |
%(msecs)d | 日志输出的时间 毫秒部分(例:504) |
%(filename)s | 日志输出所在文件名(例:test.py ) |
%(module)s | 模块名称,filename 输出为test.py module输出为test ,不带.py
|
%(pathname)s | 日志输出所在完整路径(例:/Users/wuyaa/PycharmProjects/lern_p3/test.py ) |
%(funcName)s | 日志输出所在方法名称 |
%(levelname)s | 文本日志记录级别消息,('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL') |
%(levelno)s | 对于消息数字记录级别(DEBUG,INFO, WARNING,ERROR, CRITICAL) |
%(lineno)d | 发出日志记录调用的源码行号(如果可用)。 |
%(message)s | 记录的消息,(也就是 logging.info('this is a message') 等记录的消息) |
%(name)s | 日志记录器的名称(例:root 表示是根记录器 ) |
%(process)d | 进程ID(如果可用)。 |
%(processName)s | 进程名称(如果可用) |
%(thread)d | 线程ID(如果可用)。 |
%(threadName)s | 线程名称(如果可用)。 |
4)datefmt
datefmt 是作为%(asctime)s
的补充,在 format 设置格式化时间后,可以进行更精准的格式输出,更多可以参考 time.strftime
格式 | 描述 |
---|---|
%A | 本地化的星期中每日的完整名称。 |
%B | 本地化的月完整名称。 |
%Y | 十进制数表示的带世纪的年份。 |
%m | 十进制数 [01,12] 表示的月。 |
%d | 十进制数 [01,31] 表示的月中日。 |
%H | 十进制数 [00,23] 表示的小时(24小时制)。 |
%M | 十进制数 [00,59] 表示的分钟 |
%S | 十进制数 [00,61] 表示的秒。 |
%Z | 时区名称(如果不存在时区,则不包含字符)。 |
# test.py
import logging
def main():
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s] [%(funcName)s] [%(message)s] [%(lineno)d]',
datefmt='%Y/%m/%d %I:%M:%S')
logging.info('ok')
if __name__ == '__main__':
main()
输出如下:
>>>[2019/08/05 11:25:18] [main] [ok] [10]
5)style
格式化字符串的风格,使用默认值即可,无需改动
六、从多个模块记录日志
如果你的程序包含多个模块,你并不需要在每个模块引进 logging 后都设置进行配置,而是在主函数入口设置一次即可。
# mylib.py
import logging
def do_something():
logging.info('Doing something')
# myapp.py
import logging
# 具体导入时根据你自己模块的路径,我这里是根据我自己的运行环境,最好从模块 根路径 导入,而不要使用相对路径
from learn_test.log.mylib import do_something
def main():
logging.basicConfig(level=logging.INFO,
format='[%(asctime)s] [%(funcName)s] [%(message)s] [%(lineno)d]',
datefmt='%Y/%m/%d %I:%M:%S')
logging.info('Started')
do_something()
logging.info('Finished')
if __name__ == '__main__':
main()
在程序入口设置的basicConfig,在 mylib.py 的do_something()
函数中也会起作用
七、日志进阶 记录器Logger
未完待续......
参考文件
https://docs.python.org/zh-cn/3.7/howto/logging.html
https://docs.python.org/zh-cn/3.7/library/logging.html