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()