LOGGING
example
基础
默认配置, 保存到文件和终端
import logging
import sys
stream_handler = logging.StreamHandler(sys.stdout)
file_handler = logging.FileHandler("info.log", mode="a")
logging.captureWarnings(True)
logging.basicConfig(
level=logging.INFO,
format=(
'%(asctime)s %(pathname)s[line:%(lineno)d] %(levelname)s %(message)s'
),
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[
stream_handler,
file_handler,
],
)
保存到文件的同时,输入的终端
用2个handler
import logging
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
fh = logging.FileHandler(filename='log.log')
fh.setLevel(logging.WARNING)
f_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
fh.setFormatter(f_formatter)
logger.addHandler(ch)
logger.addHandler(fh)
logger.info("info message")
logger.warn("warn message")
添加过滤器,只记录info,不记录warning 用在info的logger
import logging
logger = logging.getLogger('test_filter')
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(
logging.Formatter('%(user)s %(message)s')
)
class RamwinFilter(logging.Filter):
def filter(self, record):
record.user = 'ramwin'
if record.levelname == 'WARNING':
return False
print(record.levelname)
return True
f = RamwinFilter()
logger.addHandler(ch)
logger.addFilter(f)
logger.info("info")
logger.warning("warning")
Exceptions raised during logging
logging.Logger
propagate 设置这个值就能让不抛出LogRecord给父级
debug(msg, stack_info, *args, **kwargs)
第二个参数 stack_info 如果是 True, 就会把日志的堆栈信息打印出来
log(lvl, msg, *args, **kwargs)
lvl: 必须是整数 用指定的lvl等级去添加一个日志, 只要这个lvl大于等于20, 就会出发logging.INFO
logging.handlers
StreamHandler
默认是sys.std, 建议改成sys.stdout
/usr/lib/python3.8/logging/__init__.py
class StreamHandler(Handler):
def __init__(self, stream=None):
if stream is None:
stream = sys.stderr
所以StreamHandler的输出都是2>error哦
FileHandler
class logging.FileHandler(filename, mode='a', encoding=None, delay=False)
RotatingFileHandler
from logging.handlers import RotatingFileHandler
import humanfriendly
RotatingFileHandler(
"info.log", mode="a",
maxBytes=humanfriendly.parse_size("10MiB"), backupCount=30,
)
MemoryHandler
测试
MemoryHandler继承了BufferingHandler, 可以用来临时记录日志,一旦日志太多(超过了capacity),或者等级太高(达到了flushLevel), 就会记录到(target)
如果需要抛弃刚才的日志,可以调用log.handelrs[0].close()或者log.removeHandler(memory_handler)
class logging.handlers.MemoryHandler(capacity, flushLevel=ERROR, target=None)
其他
[ ] NullHandler
[ ] WatchedFileHadnler
[ ] BaseRotatingHandler
[ ] TimedRotatingFileHandler
[ ] SocketHandler
[ ] DatagramHandler
[ ] SysLogHandler
[ ] NTEventLogHandler
[ ] SMTPHandler
[ ] HTTPHandler
[ ] QueueHandler
[ ] QueueListener
LogRecord
LogRecord属性
%(asctime)s: 时间%(created)s: 时间戳%(filename)s: 文件名%(pathname)s: 路径名%(levelname)s: INFO, ERROR等级信息%(message)s: 消息内容%(module)s: 模块名,文件名.stem%(process)d: 进程ID%(processName)d: 进程名
模块级函数
basicConfig(**kwargs)
因为formatter的设置是在basicConfig里设置的, 所以basicConfig以后再给root添加logger就没有formatter的效果了(这样可以避免每个recorder都要判断formatter是否存在)
basicConfig只能调用一次, 后续调用没效果.
for h in root.handlers[:]: # 清理旧的handler
root.removeHandler(h)
h.close()
for h in handlers: # 设置新的handler
if h.formatter is None:
h.setFormatter(fmt)
shutdown
系统退出时自动调用, 不要手动调用
调用时会让每个logger都调用flush后close
warnings
logging.captureWarnings(True)
拓展功能
根据函数自动缩进
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Xiang Wang <ramwin@qq.com>
import logging
import traceback
class IndentFormatter(logging.Formatter):
def __init__(self, indent=0, *args, **kwargs):
self.indent = indent
super().__init__(*args, **kwargs)
def format(self, record: logging.LogRecord):
stack_indent = len(traceback.format_stack())
indent = self.indent
if record.msg.startswith("group: "):
self.indent += 1
if record.msg.startswith("groupend: "):
self.indent -= 1
indent -= 1
return " " * max(indent + stack_indent, 0) + super().format(record)
handler = logging.StreamHandler()
handler.setFormatter(IndentFormatter(-10, "%(message)s"))
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
def test():
logger.info("test")
def main():
logger.info("group: main")
logger.info("main")
test()
logger.info("groupend: main")
if __name__ == "__main__":
main()