1. 日志工具
代码检查,debug,调优都只能让代码确保当时是可靠的,一些复杂的关联错误,也可能让这些测试呀debug呀失准,,而只有日志才能长期的帮助我们监控项目的健壮性.这种时候就可以使用标准库logging为程序的运行做记录,在试运行之后通过分析logging记录的方式来debug.
在logging框架下首先我们需要初始化一个logger来处理log,之后通过添加handler,Formatter和config子属性来自定义我们的logger.
一个简单的例子
import logging import sys #日志的名字,会在每行的一开始写 logger = logging.getLogger("endlesscode") #格式化 formatter = logging.Formatter('%(name)-12s %(asctime)s %(levelname)-8s %(message)s', '%a, %d %b %Y %H:%M:%S',) #设定输出文件 file_handler = logging.FileHandler("src/test.log") #为handler设置输出格式 file_handler.setFormatter(formatter) #流控制,将信息输出到标准流输出 stream_handler = logging.StreamHandler(sys.stderr) #为logger设置handler logger.addHandler(file_handler) #发送信息到流 logger.addHandler(stream_handler) #设置报错等级 #logger.setLevel(logging.ERROR) #报错 logger.error("w") #移除handler logger.removeHandler(stream_handler) #报错 logger.error("f")
w
其中
level: 设置日志级别,默认为logging.WARNING
stream: 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略
1.1. 输出文本的格式化
元素 | 格式化字符串 | 描述 |
---|---|---|
args | 不用格式化 | 参数会是一个元组 |
asctime | %(asctime)s | 可读的时间 |
created | %(created)f | 记录的创建时间 |
filename | %(filename)s | 文件名 |
funcName | %(funcName)s | 函数名 |
levelname | %(levelname)s | 错误,警报等的名字 |
levelno | %(levelno)s | 错误,警报等,是预警等级 |
lineno | %(lineno)d | 报错行数 |
module | %(module)s | 报错模块 |
msecs | %(msecs)d | 毫秒级的出错时间 |
message | %(message)s | 错误信息 |
name | %(name)s | log的名字 |
pathname | %(pathname)s | 报错文件所在path |
process | %(process)d | 进程id |
processName | %(processName)s | 进程名 |
relativeCreated | %(relativeCreated)d | 微秒级的报错时间 |
thread | %(thread)d | 线程id |
threadName | %(threadName)s | 线程名 |
1.2. 日志回滚
日志也不是一直记录就好,也要考录时效性和存储空间的限制,回滚机制便是解决这个问题的
from logging.handlers import RotatingFileHandler #定义一个RotatingFileHandler,最多备份5个日志文件,每个日志文件最大10M Rthandler = RotatingFileHandler('src/myapp.log', maxBytes=10*1024*1024,backupCount=5) Rthandler.setLevel(logging.INFO) formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') Rthandler.setFormatter(formatter) logging.getLogger('').addHandler(Rthandler)
1.3. 几种handler
StreamHandler(stream=None) 流输出
FileHandler(filename, mode='a', encoding=None, delay=False) 写入文件
WatchedFileHandler(filename[, mode[, encoding[, delay]]]) 监控log文件
RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0) 轮替日志,根据日志文件的大小来循环
TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None) 轮替日志,根据时间来循环,interval参数可选的值有:
- "S"-Seconds
- 'M'-Minutes
- 'H'-Hours
- 'D'-Days
- 'W0'~'W6'-Weekday (0=Monday)
- 'midnight'-半夜循环
SocketHandler(host, port) 把log送到网上的socket
DatagramHandler(host, port) 把log送到网上的UDP sockets
SysLogHandler(address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER, socktype=socket.SOCK_DGRAM) log送到unix系统log
SMTPHandler(mailhost, fromaddr, toaddrs, subject, credentials=None, secure=None, timeout=1.0) log送到电子邮箱
MemoryHandler(capacity, flushLevel=ERROR, target=None) log存入内存
HTTPHandler(host, url, method='GET', secure=False, credentials=None, context=None) log通过http网络送到服务器
1.4. 使用.conf设置文件设置logging行为
当然可以在程序中设置log了,但为了改起来方便也可以写在别的文件中然后用config.fileConfig(path)
来设置,配置文件的形式是这样:
[loggers] keys=root,simpleExample [handlers] keys=consoleHandler [formatters] keys=simpleFormatter [logger_root] level=DEBUG handlers=consoleHandler [logger_simpleExample] level=DEBUG handlers=consoleHandler qualname=simpleExample propagate=0 [handler_consoleHandler] class=StreamHandler level=DEBUG formatter=simpleFormatter args=(sys.stdout,) [formatter_simpleFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt=%a, %d %b %Y %H:%M:%S
要注意的是如果用这种方式那么,使用rotation file handler
时,不要同时声明file handler
,否则rotation
发生时,doRollover
函数的os.rename
会报错(「另一个程序正在使用此文件,进程无法访问).当然,可以写另一个py文件专门用来初始化,要用的时候import
进来就好了.
1.5. 使用字典配置logging行为
上面一种看起来比较晦涩难懂难以维护,更加pythonic的做法是使用字典进行配置
import sys import logging import logging.config LOGGING_CONFIG = dict( version=1, loggers={ "<模块>":{ "level": "INFO", "handlers": ["model_console"] }, "<服务>": { "level": "INFO", "handlers": ["server_console"] } }, handlers={ "model_console": { "class": "logging.StreamHandler", "formatter": "model", "stream": sys.stdout }, "server_console": { "class": "logging.StreamHandler", "formatter": "server", "stream": sys.stdout } }, formatters={ "model": { "format": "%(asctime)s :: %(name)s :: %(levelname)s :: %(process)d :: "+ "%(module)s - line %(lineno)d - funcname: %(funcName)s - params: %(params)s :: %(message)s", "datefmt": "[%Y-%m-%d %H:%M:%S %z]", "class": "logging.Formatter" }, "server": { "format": "%(asctime)s :: %(name)s :: %(levelname)s :: %(host)s :: " + "%(request)s :: %(message)s", "datefmt": "[%Y-%m-%d %H:%M:%S %z]", "class": "logging.Formatter" } } )
logging.config.dictConfig(LOGGING_CONFIG) model_logger = logging.getLogger('<模块>') server_logger = logging.getLogger('<服务>') def a(): model_logger.info("qwer",extra= dict(params = ["123",12]))
a()
[2018-06-06 17:35:01 +0800] :: <模块> :: INFO :: 27392 :: <ipython-input-7-0aa5e3aa1d5b> - line 6 - funcname: a - params: ['123', 12] :: qwer
1.6. 结构化log
现在很多场景尤其是ELK体系下的log要求使用json格式.我们可以借助了第三方工具structlog用于结构化log数据
结构化log需要配置两块:
- structlog部分需要配置
import structlog structlog.configure( processors=[ structlog.stdlib.filter_by_level, # 判断是否接受某个level的log消息 structlog.stdlib.add_logger_name, # 增加字段logger structlog.stdlib.add_log_level, #增加字段level structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.TimeStamper(fmt="iso"), #增加字段timestamp且使用iso格式输出 structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info,# 捕获异常的栈信息 structlog.processors.StackInfoRenderer(), # 详细栈信息 structlog.processors.JSONRenderer() #json格式输出,第一个参数会被放入event字段 ], context_class=dict, logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, )
- 配置标准库的log
import logging import sys handler = logging.StreamHandler(sys.stdout) root_logger = logging.getLogger() root_logger.addHandler(handler) root_logger.setLevel(logging.INFO) # 设置最低log等级
之后使用structlog.get_logger
构造logger,就可以使用它来输出结构化的log了
w
0
w
1
w
2
w
3
我们还可以使用structlog.processors.format_exc_info
来自动捕获异常信息,使用structlog.processors.StackInfoRenderer()
来获取栈信息
w
4
w
5
还没有评论,来说两句吧...