在 Python 中,怎样才能算作一个比较标准的日志记录过程呢?或许很多人会使用 print 语句输出一些运行信息,然后再在控制台观察,运行的时候再将输出重定向到文件输出流保存到文件中,这样其实是非常不规范的,在 Python 中有一个标准的 logging 模块,我们可以使用它来进行标注的日志记录,利用它我们可以更方便地进行日志记录,同时还可以做更方便的级别区分以及一些额外日志信息的记录,如时间、运行模块信息等。
logging模块简介
logging模块是Python的一个标准库模块,开发过程中,可以通过该模块,灵活的完成日志的记录。
logging模块提供了两种记录日志的方式:
1)使用logging提供的模块级别的函数(logging.basicConfig,logging.debug,logging.info…)
2)使用logging模块的组件(loggers,handlers,filters,formatters)这里建议使用第二种方式
logging模块的日志级别
DEBUG < INFO < WARNING < ERROR < CRITICAL
日志级别(level) | 描述 |
---|---|
DEBUG | 调试级别,一般用于问题的排查,日志的信息最为详细 |
INFO | 仅记录普通的信息,日志信息的详细程度仅次于DEBUG |
WARNING | 警告信息,一般这类信息不会影响程序的正常运行 |
ERROR | 错误信息, 出现错误信息时,程序一般已不能正常运行 |
CRITICAL | 更严重的错误信息,程序不能继续运行 |
import logging
logging.info('This is a log info')
logging.debug('Debugging')
logging.warning('Warning exists')
logging.info('Finish')
我们发现 DEBUG 的信息是没有输出的,这是因为我们在全局配置的时候设置了输出为 INFO 级别,所以 DEBUG 级别的信息就被过滤掉了。 这时如果我们将输出的日志级别设置为 DEBUG,就可以看到 DEBUG 级别的日志输出了:
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
basicConfig是logging模块的全局配置方法
关键字 | 描述 |
---|---|
filename | 创建一个FileHandler,使用指定的文件名,而不是使用StreamHandler。 |
filemode | 如果指明了文件名,指明打开文件的模式(如果没有指明filemode,默认为'a')。 |
format | handler使用指明的格式化字符串。 |
datefmt | 使用指明的日期/时间格式。 |
level | 指明根logger的级别。 |
stream | 使用指明的流来初始化StreamHandler。该参数与'filename'不兼容,如果两个都有,'stream'被忽略。 |
格式 | 描述 |
---|---|
%(levelno)s | 打印日志级别的数值 |
%(levelname)s | 打印日志级别名称 |
%(pathname)s | 打印当前执行程序的路径 |
%(filename)s | 打印当前执行程序名称 |
%(funcName)s | 打印日志的当前函数 |
%(lineno)d | 打印日志的当前行号 |
%(asctime)s | 打印日志的时间 |
%(thread)d | 打印线程id |
%(threadName)s | 打印线程名称 |
%(process)d | 打印进程ID |
%(message)s | 打印日志信息 |
import logging
logging.basicConfig(level=logging.DEBUG,
filename='output.log',
datefmt='%Y/%m/%d %H:%M:%S',
format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s')
logging.info('This is a log info')
logging.debug('Debugging')
logging.warning('Warning exists')
logging.info('Finish')
四大组件
组件名称 | 功能描述 |
---|---|
Logger | 日志器,提供了应用程序可一直使用的接口 |
Handler | 负责日志记录的传输目的地,比如有FileHandler(写入目标为文件)和StreamHandler(写入目标为流,默认为标准输出流) |
Filter | 负责过滤哪些日志是要输出的,若有多个 Logger,可根据名称过滤出指定的 Logger 来记录日志 |
Formatter | 负责对日志输出格式的格式化 |
1. Logger
1)logging.getLogger(name) 指定name,返回一个名称为name的Logger实例。如果再次使用相同的名字,是实例化一个对象。未指定name,返回Logger实例,名称是root,即根Logger。
2)创建一个或多个 handler,用于指定日志信息的输出流向
3)创建一个或多个 formatter,指定日志的格式,并分别将 formatter 绑定到 上
4)将 handler 绑定到 logger对象 上
5)logger.setLevel(logging.DEBUG) 设置日志级别
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')
2.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 服务器。
import logging
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)
# Log
logger.info('This is a log info')
logger.debug('Debugging')
logger.warning('Warning exists')
logger.info('Finish')
3.Formatter
在进行日志格式化输出的时候,我们可以不借助于 basicConfig 来全局配置格式化输出内容,可以借助于 Formatter 来完成,下面我们再来单独看下 Formatter 的用法:
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 对象即可
设置format中特殊字符asctime(日期时间)的输出格式
- 特殊字符:
- %y 两位数的年份表示(00-99)
- %Y 四位数的年份表示(000-9999)
- %m 月份(01-12)
- %d 月内中的一天(0-31)
- %H 24小时制小时数(0-23)
- %I 12小时制小时数(01-12)
- %M 分钟数(00=59)
- %S 秒(00-59)
使用自己想要的分隔符和顺序来定义日期时间的格式,例如:
datefmt='%m/%d/%Y %I:%M:%S %p'
4.Filter
决定一个日志记录是否发送到handler。
import logging
logger1 = logging.getLogger('aaa')
logger2 = logging.getLogger('bbb')
# 定义一个 filter
filter = logging.Filter(name='bbb')
# 定义一个 handler
ch = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
# 若两个 logger对象 的日志级别相同,且都是用通过一个 handler,可以在这个 handler 上设置日志级别
ch.setLevel(logging.ERROR)
# 在 handler 上放置过滤器
ch.addFilter(filter)
logger1.addHandler(ch)
logger2.addHandler(ch)
logger1.error('logger1 error message')
logger2.error('logger2 error message')
因为对handler增加一个过滤器,过滤器就像一个筛子,只筛选出我们想要的日志,上图代码中的过滤器只接收名为bbb的loger产生的日志