在这之前我还是用 print 更多一点,但是现在意识到,print 无法方便修改日志等级,也无法方便打印具体的模块与函数,所以打算以后都用 logging 来。

logging 本身是 python 内置的一个库,用起来也很方便。现在备份一点最小的可运行的配置 setup_logging.py

import json
import logging.config
import os

def setup_logging(
    default_path='logging.json',
    default_level=logging.INFO,
    env_key='LOG_CFG'
):
    """Setup logging configuration
    """
    path = default_path
    value = os.getenv(env_key, None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path, 'rt') as f:
            config = json.load(f)
        logging.config.dictConfig(config)
    else:
        logging.basicConfig(level=default_level)

这里包含的 setup_logging 函数所做的工作就是读取本地的 logging.json 日志配置文件,然后设置输出。

配置文件如下 logging.json

{
    "version": 1,
    "disable_existing_loggers": false,
    "formatters": {
        "simple": {
            "format": "%(asctime)s, %(levelname)-6s [%(filename)s:%(lineno)d] %(message)s"
        }
    },

    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "formatter": "simple",
            "stream": "ext://sys.stdout"
        },

        "info_file_handler": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "INFO",
            "formatter": "simple",
            "filename": "info.log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        },

        "error_file_handler": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "ERROR",
            "formatter": "simple",
            "filename": "errors.log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        }
    },

    "loggers": {
        "my_module": {
            "level": "ERROR",
            "handlers": ["console"],
            "propagate": "no"
        }
    },

    "root": {
        "level": "INFO",
        "handlers": ["console", "info_file_handler", "error_file_handler"]
    }
}

在运行时可以这样来:

$ LOG_CFG=logging.json python my_server.py

如果你喜欢 yaml 配置版本,那么可以用这个:

import os
import logging.config

import yaml

def setup_logging(
    default_path='logging.yaml',
    default_level=logging.INFO,
    env_key='LOG_CFG'
):
    """Setup logging configuration

    """
    path = default_path
    value = os.getenv(env_key, None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path, 'rt') as f:
            config = yaml.safe_load(f.read())
        logging.config.dictConfig(config)
    else:
        logging.basicConfig(level=default_level)

以及 yaml 版本的配置文件 logging.yaml

version: 1
disable_existing_loggers: False
formatters:
    simple:
        format: "%(asctime)s, %(levelname)-6s [%(filename)s:%(lineno)d] %(message)s"

handlers:
    console:
        class: logging.StreamHandler
        level: DEBUG
        formatter: simple
        stream: ext://sys.stdout

    info_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: INFO
        formatter: simple
        filename: info.log
        maxBytes: 10485760 # 10MB
        backupCount: 20
        encoding: utf8

    error_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: ERROR
        formatter: simple
        filename: errors.log
        maxBytes: 10485760 # 10MB
        backupCount: 20
        encoding: utf8

loggers:
    my_module:
        level: ERROR
        handlers: [console]
        propagate: no

root:
    level: INFO
    handlers: [console, info_file_handler, error_file_handler]

不过 yaml 由于不是 python 默认自带的库,需要你自己手动装一下。

还有在使用 logging 的时候有个小技巧,通过调用 logger.exception(msg) 时还会把当前抛出的错误的调用栈打印出来,当然也可以用 logger.error(msg, exc_info=True) 来打印。

参考:https://logmatic.io/blog/python-logging-with-json-steroids/