logging日志
学习目标
- 能够知道logging日志的使用
1. logging日志的介绍
在现实生活中,记录日志非常重要,比如:银行转账时会有转账记录;飞机飞行过程中,会有个黑盒子(飞行数据记录器)记录着飞机的飞行过程,那在咱们python程序中想要记录程序在运行时所产生的日志信息,怎么做呢?
可以使用 logging 这个包来完成
记录程序日志信息的目的是:
-
可以很方便的了解程序的运行情况
-
可以分析用户的操作行为、喜好等信息
-
方便开发人员检查bug
2. logging日志级别介绍
日志等级可以分为5个,从低到高分别是:
-
DEBUG
-
INFO
-
WARNING
-
ERROR
-
CRITICAL
日志等级说明:
-
DEBUG:程序调试bug时使用
-
INFO:程序正常运行时使用
-
WARNING:程序未按预期运行时使用,但并不是错误,如:用户登录密码错误
-
ERROR:程序出错误时使用,如:IO操作失败
-
CRITICAL:特别严重的问题,导致程序不能再继续运行时使用,如:磁盘空间为空,一般很少使用
-
默认的是WARNING等级,当在WARNING或WARNING之上等级的才记录日志信息。
-
日志等级从低到高的顺序是: DEBUG < INFO < WARNING < ERROR < CRITICAL
3. logging日志的使用
在 logging 包中记录日志的方式有两种:
-
输出到控制台
-
保存到日志文件
日志信息输出到控制台的示例代码:
import logging
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
运行结果:
WARNING:root:这是一个warning级别的日志信息
ERROR:root:这是一个error级别的日志信息
CRITICAL:root:这是一个critical级别的日志信息
说明:
- 日志信息只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING
logging日志等级和输出格式的设置:
import logging
# 设置日志等级和输出日志格式
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
运行结果:
2019-02-13 20:41:33,080 - hello.py[line:6] - DEBUG: 这是一个debug级别的日志信息
2019-02-13 20:41:33,080 - hello.py[line:7] - INFO: 这是一个info级别的日志信息
2019-02-13 20:41:33,080 - hello.py[line:8] - WARNING: 这是一个warning级别的日志信息
2019-02-13 20:41:33,080 - hello.py[line:9] - ERROR: 这是一个error级别的日志信息
2019-02-13 20:41:33,080 - hello.py[line:10] - CRITICAL: 这是一个critical级别的日志信息
代码说明:
-
level 表示设置的日志等级
-
format 表示日志的输出格式, 参数说明:
-
%(levelname)s: 打印日志级别名称
-
%(filename)s: 打印当前执行程序名
-
%(lineno)d: 打印日志的当前行号
-
%(asctime)s: 打印日志的时间
-
%(message)s: 打印日志信息
-
日志信息保存到日志文件的示例代码:
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
filename="log.txt",
filemode="a",
datefmt=''%a %d %b %Y %H:%M:%S', # 日期格式同time.strftime()
handlers=[fh])
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
其中,fh = logging.FileHandler("./logs/flask.log",encoding= 'utf-8')
格式format说明
%(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:进程名
%(message)s: 打印日志信息
运行结果:
4. logging日志在mini-web项目中应用
web.py 程序使用logging日志示例:
- 程序入口模块设置logging日志的设置
import socket
import threading
import sys
import framework
import logging
# logging日志的配置
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
filename="log.txt",
filemode="w")
- INFO级别的日志输出,示例代码:
# 判断是否是动态资源请求
if request_path.endswith(".html"):
"""这里是动态资源请求,把请求信息交给框架处理"""
logging.info("动态资源请求:" + request_path)
...
else:
"""这里是静态资源请求"""
logging.info("静态资源请求:" + request_path)
...
- WARNING级别的日志输出,示例代码:
# 获取命令行参数判断长度
if len(sys.argv) != 2:
print("执行命令如下: python3 xxx.py 9000")
logging.warning("用户在命令行启动程序参数个数不正确!")
return
# 判断端口号是否是数字
if not sys.argv[1].isdigit():
print("执行命令如下: python3 xxx.py 9000")
logging.warning("用户在命令行启动程序参数不是数字字符串!")
return
framework.py 程序使用logging日志示例:
- ERROR级别的日志输出,示例代码:
# 处理动态资源请求
def handle_request(env):
# 获取动态请求资源路径
request_path = env["request_path"]
print("接收到的动态资源请求:", request_path)
# 遍历路由列表,选择执行的函数
for path, func in route_list:
if request_path == path:
result = func()
return result
else:
logging.error("没有设置相应的路由:" + request_path)
# 没有找到动态资源
result = not_found()
return result
说明:
-
logging日志配置信息在程序入口模块设置一次,整个程序都可以生效。
- logging.basicConfig 表示 logging 日志配置操作
5. 小结
-
记录python程序中日志信息使用 logging 包来完成
-
logging日志等级有5个:
-
DEBUG
-
INFO
-
WARNING
-
ERROR
-
CRITICAL
-
-
打印(记录)日志的函数有5个:
-
logging.debug函数, 表示: 打印(记录)DEBUG级别的日志信息
-
logging.info函数, 表示: 打印(记录)INFO级别的日志信息
-
logging.warning函数, 表示: 打印(记录)WARNING级别的日志信息
-
logging.error函数, 表示: 打印(记录)ERROR级别的日志信息
-
logging.critical函数, 表示: 打印(记录)CRITICAL级别的日志信息
-
将日志输出到屏幕及文件
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
#创建日志对像
logger=logging.getLogger("TEST-LOG")
logger.setLevel(logging.ERROR) #日志级别
#创建日志输出到屏幕及设置级别
ch=logging.StreamHandler()
ch.setLevel(logging.DEBUG)
#创建日志输出到文件及设置级别
fh=logging.FileHandler("access.log",encoding='UTF-8')
fh.setLevel(logging.WARNING)
#设置日志格式
formatter=logging.Formatter('%(asctime)s-%(name)s-%(levelname)s-%(messages)s')
#将格式付给屏幕和文件
ch.setFormatter(formatter)
fh.setFormatter(formatter)
#将两个输出付给对像logger
logger.addHandler(ch)
logger.addHandler(fh)
#生成日志
logger.debug("debug messsage")
logger.info("info.message")
logger.warn("warn.message")
logger.error("error.message")
logger.critical("critical.message")
logger可直接在func里用全局变量. 但在class无效,需要传入
只设置了文件日志,docker logs 也会打印日志信息,但不会打印formatter部分。
参考别人的一种设置(测试,起了文件,但没写进去)
logconfig_dict = {
'version':1,
'disable_existing_loggers': True,
'loggers':{
"gunicorn.error": {
"level": "DEBUG",# 打日志的等级可以换的,下面的同理
"handlers": ["error_file"], # 对应下面的键
"propagate": 1,
"qualname": "gunicorn.error"
},
"gunicorn.access": {
"level": "INFO",
"handlers": ["access_file"],
"propagate": 0,
"qualname": "gunicorn.access"
}
},
'handlers':{
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "access"
},
"error_file": {
"encoding":"utf8",
"level":"ERROR",
"class": "logging.handlers.RotatingFileHandler",
# "maxBytes": 1024*1024*1024,# 打日志的大小,我这种写法是1个G
# "backupCount": 1,# 备份多少份,经过测试,最少也要写1,不然控制不住大小
"formatter": "generic",# 对应下面的键
# 'mode': 'w+',
"filename": "./logs/erro.log"# 打日志的路径
},
"access_file": {
"encoding":"UTF-8",
"level":"INFO",
"class": "logging.handlers.RotatingFileHandler",
# "maxBytes": 1024*1024*1024,
# "backupCount": 1,
"formatter": "generic",
"filename": "./logs/access.log",
}
},
'formatters':{
"generic": {
"format": "[%(asctime)s] %(levelname)s [%(filename)s:%(lineno)s] %(message)s", # 打日志的格式
"datefmt": "[%Y-%m-%d %H:%M:%S %z]",# 时间显示方法
"class": "logging.Formatter"
},
"access": {
"format": "[%(asctime)s] %(levelname)s [%(filename)s:%(lineno)s] %(message)s",
"class": "logging.Formatter"
}
}
}
Python模块之Logging(四)——常用handlers的使用
一、StreamHandler
流handler——包含在logging模块中的三个handler之一。
能够将日志信息输出到sys.stdout, sys.stderr 或者类文件对象(更确切点,就是能够支持write()和flush()方法的对象)。
只有一个参数:
class logging.StreamHandler(stream=None)
日志信息会输出到指定的stream中,如果stream为空则默认输出到sys.stderr。
二、FileHandler
logging模块自带的三个handler之一。继承自StreamHandler。将日志信息输出到磁盘文件上。
构造参数:
class logging.FileHandler(filename, mode='a', encoding=None, delay=False)
模式默认为append,delay为true时,文件直到emit方法被执行才会打开。默认情况下,日志文件可以无限增大。
三、NullHandler
空操作handler,logging模块自带的三个handler之一。 没有参数。
四、WatchedFileHandler
位于logging.handlers模块中。用于监视文件的状态,如果文件被改变了,那么就关闭当前流,重新打开文件,创建一个新的流。由于newsyslog或者logrotate的使用会导致文件改变。这个handler是专门为linux/unix系统设计的,因为在windows系统下,正在被打开的文件是不会被改变的。 参数和FileHandler相同:
class logging.handlers.WatchedFileHandler(filename, mode='a', encoding=None, delay=False)
五、RotatingFileHandler
位于logging.handlers支持循环日志文件。
class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0)
参数maxBytes和backupCount允许日志文件在达到maxBytes时rollover.当文件大小达到或者超过maxBytes时,就会新创建一个日志文件。上述的这两个参数任一一个为0时,rollover都不会发生。也就是就文件没有maxBytes限制。backupcount是备份数目,也就是最多能有多少个备份。命名会在日志的base_name后面加上.0-.n的后缀,如example.log.1,example.log.1,…,example.log.10。当前使用的日志文件为base_name.log。
六、TimedRotatingFileHandler
定时循环日志handler,位于logging.handlers,支持定时生成新日志文件。
class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False)
参数when决定了时间间隔的类型,参数interval决定了多少的时间间隔。如when=‘D’,interval=2,就是指两天的时间间隔,backupCount决定了能留几个日志文件。超过数量就会丢弃掉老的日志文件。
when的参数决定了时间间隔的类型。两者之间的关系如下:
'S' | 秒
'M' | 分
'H' | 时
'D' | 天
'W0'-'W6' | 周一至周日
'midnight' | 每天的凌晨
utc参数表示UTC时间。
七、其他handler——SocketHandler、DatagramHandler、SysLogHandler、NtEventHandler、SMTPHandler、MemoryHandler、HTTPHandler
这些handler都不怎么常用,所以具体介绍就请参考官方文档 其他handlers
下面使用简单的例子来演示handler的使用:
例子一——不使用配置文件的方式(StreamHandler):
import logging
# set up logging to file - see previous section for more details
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
datefmt='%m-%d %H:%M',
filename='/temp/myapp.log',
filemode='w')
# define a Handler which writes INFO messages or higher to the sys.stderr
#
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# set a format which is simpler for console use
#设置格式
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
# tell the handler to use this format
#告诉handler使用这个格式
console.setFormatter(formatter)
# add the handler to the root logger
#为root logger添加handler
logging.getLogger('').addHandler(console)
# Now, we can log to the root logger, or any other logger. First the root...
#默认使用的是root logger
logging.info('Jackdaws love my big sphinx of quartz.')
# Now, define a couple of other loggers which might represent areas in your
# application:
logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')
logger1.debug('Quick zephyrs blow, vexing daft Jim.')
logger1.info('How quickly daft jumping zebras vex.')
logger2.warning('Jail zesty vixen who grabbed pay from quack.')
logger2.error('The five boxing wizards jump quickly.')
输出到控制台的结果:
root : INFO Jackdaws love my big sphinx of quartz.
myapp.area1 : INFO How quickly daft jumping zebras vex.
myapp.area2 : WARNING Jail zesty vixen who grabbed pay from quack.
myapp.area2 : ERROR The five boxing wizards jump quickly.
例子二——使用配置文件的方式(TimedRotatingFileHandler) :
log.conf 日志配置文件:
[loggers]
keys=root,test.subtest,test
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=INFO
handlers=consoleHandler,fileHandler
[logger_test]
level=INFO
handlers=consoleHandler,fileHandler
qualname=tornado
propagate=0
[logger_test.subtest]
level=INFO
handlers=consoleHandler,fileHandler
qualname=rocket.raccoon
propagate=0
[handler_consoleHandler] #输出到控制台的handler
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler] #输出到日志文件的handler
class=logging.handlers.TimedRotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=('rocket_raccoon_log','midnight')
[formatter_simpleFormatter]
format=[%(asctime)s-%(name)s(%(levelname)s)%(filename)s:%(lineno)d]%(message)s
datefmt=logging.config.fileConfig('conf/log.conf')
logger = getLogging()
获取logger方法:
def getLogging():
return logging.getLogger("test.subtest")
配置logger并且调用:
logging.config.fileConfig('conf/log.conf')
logger = getLogging()
logger.info("this is an example!")
控制台和日志文件中都会输出:
[2016-07-01 09:22:06,470-test.subtest(INFO)main.py:55]this is an example!
滚动日志与过期删除(按时间)
# coding:utf-8
import logging
import time
import re
from logging.handlers import TimedRotatingFileHandler
from logging.handlers import RotatingFileHandler
def backroll():
#日志打印格式
log_fmt = '%(asctime)s\tFile \"%(filename)s\",line %(lineno)s\t%(levelname)s: %(message)s'
formatter = logging.Formatter(log_fmt)
#创建TimedRotatingFileHandler对象
log_file_handler = TimedRotatingFileHandler(filename="ds_update", when="M", interval=2, backupCount=2)
#log_file_handler.suffix = "%Y-%m-%d_%H-%M.log"
#log_file_handler.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}.log$")
log_file_handler.setFormatter(formatter)
logging.basicConfig(level=logging.INFO)
log = logging.getLogger()
log.addHandler(log_file_handler)
#循环打印日志
log_content = "test log"
count = 0
while count < 30:
log.error(log_content)
time.sleep(20)
count = count + 1
log.removeHandler(log_file_handler)
if __name__ == "__main__":
backroll()
filename:日志文件名的prefix;
when:是一个字符串,用于描述滚动周期的基本单位,字符串的值及意义如下: “S”: Seconds “M”: Minutes “H”: Hours “D”: Days “W”: Week day (0=Monday) “midnight”: Roll over at midnight
interval: 滚动周期,单位有when指定,比如:when=’D’,interval=1,表示每天产生一个日志文件
backupCount: 表示日志文件的保留个数
几个重要的概念
-
Logger 记录器,暴露了应用程序代码能直接使用的接口。
-
Handler 处理器,将(记录器产生的)日志记录发送至合适的目的地。
-
Filter 过滤器,提供了更好的粒度控制,它可以决定输出哪些日志记录。
-
Formatter 格式化器,指明了最终输出中日志记录的布局。
Logger 记录器
Logger是一个树形层级结构,在使用接口debug,info,warn,error,critical之前必须创建Logger实例,即创建一个记录器,如果没有显式的进行创建,则默认创建一个root logger,并应用默认的日志级别(WARN),处理器Handler(StreamHandler,即将日志信息打印输出在标准输出上),和格式化器Formatter(默认的格式即为第一个简单使用程序中输出的格式)。
创建方法:
logger = logging.getLogger(logger_name)
创建Logger实例后,可以使用以下方法进行日志级别设置,增加处理器Handler。
-
logger.setLevel(logging.ERROR) # 设置日志级别为ERROR,即只有日志级别大于等于ERROR的日志才会输出
-
logger.addHandler(handler_name) # 为Logger实例增加一个处理器
-
logger.removeHandler(handler_name) # 为Logger实例删除一个处理器
Handler 处理器
Handler处理器类型有很多种,比较常用的有三个,StreamHandler,FileHandler,NullHandler,详情可以访问Python logging.handlers
创建StreamHandler之后,可以通过使用以下方法设置日志级别,设置格式化器Formatter,增加或删除过滤器Filter。
-
ch.setLevel(logging.WARN) # 指定日志级别,低于WARN级别的日志将被忽略
-
ch.setFormatter(formatter_name) # 设置一个格式化器formatter
-
ch.addFilter(filter_name) # 增加一个过滤器,可以增加多个
-
ch.removeFilter(filter_name) # 删除一个过滤器
StreamHandler
创建方法:
sh = logging.StreamHandler(stream=None)
FileHandler
创建方法:
fh = logging.FileHandler(filename, mode='a', encoding=None, delay=False)
NullHandler
NullHandler类位于核心logging包,不做任何的格式化或者输出。 本质上它是个“什么都不做”的handler,由库开发者使用。
Formatter 格式化器
使用Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S。
创建方法:
formatter = logging.Formatter(fmt=None, datefmt=None)
其中,fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,将使用'%(message)s'。如果不指明datefmt,将使用ISO8601日期格式。
Filter 过滤器
Handlers和Loggers可以使用Filters来完成比级别更复杂的过滤。Filter基类只允许特定Logger层次以下的事件。例如用‘A.B’初始化的Filter允许Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’等记录的事件,logger‘A.BB’, ‘B.A.B’ 等就不行。 如果用空字符串来初始化,所有的事件都接受。
创建方法:
filter = logging.Filter(name='')
网友评论