大虾居

谈笑有鸿儒,往来无白丁。

0%

便于快速使用的Python Logging脚手架

python logging 是一套灵活强大的日志输出框架,具体功能在这里就不赘述了。本文通过gist上的一份共享代码介绍在各种不同使用场景下如何复用一套logging配置入口来高效地使用python logging.

源码

原版代码受这位仁兄启发, 做了一些改进,修改后的代码

源代码分两部分,一部分是python入口,log.py可以放置在脚本文件夹下,也可以放置在包中。

import os
import yaml
import logging.config
import logging
import coloredlogs

def setup_logging(default_path='logging.yaml', default_level=logging.INFO, env_key='LOG_CFG'):
    """
    | **@author:** Prathyush SP
    | Logging Setup
    """
    path = default_path
    value = os.getenv(env_key, None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path, 'rt') as f:
            try:
                config = yaml.safe_load(f.read())
                logging.config.dictConfig(config)
                coloredlogs.install()
            except Exception as e:
                print(e)
                print('Error in Logging Configuration. Using default configs')
                logging.basicConfig(level=default_level)
                coloredlogs.install(level=default_level)
    else:
        logging.basicConfig(level=default_level)
        coloredlogs.install(level=default_level)
        print('Failed to load configuration file. Using default configs')

配置文件可以在部署时按需修改,解耦了日志编写逻辑和日志输出方式。配置文件用yaml格式编写,具有较好的可读性,对编辑也较为友好。默认从当前运行文件夹下加载logging.yaml。注意使用yaml需要安装额外的包pyaml

version: 1
disable_existing_loggers: false

formatters:
    standard:
        format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    error:
        format: "%(levelname)s <PID %(process)d:%(processName)s> %(name)s.%(funcName)s(): %(message)s"

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

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

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

    debug_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: DEBUG
        formatter: standard
        filename: /tmp/debug.log
        maxBytes: 10485760 # 10MB
        backupCount: 20
        encoding: utf8

    critical_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: CRITICAL
        formatter: standard
        filename: /tmp/critical.log
        maxBytes: 10485760 # 10MB
        backupCount: 20
        encoding: utf8

    warn_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: WARN
        formatter: standard
        filename: /tmp/warn.log
        maxBytes: 10485760 # 10MB
        backupCount: 20
        encoding: utf8

root:
    level: NOTSET
    handlers: [console]
    propogate: yes

loggers:
    <module>:
        level: INFO
        handlers: [console, info_file_handler, error_file_handler, critical_file_handler, debug_file_handler, warn_file_handler]
        propogate: no

    <module.x>:
        level: DEBUG
        handlers: [info_file_handler, error_file_handler, critical_file_handler, debug_file_handler, warn_file_handler]
        propogate: yes

说明

disable_existing_loggers

由于logger在程序包import时就会实例化,可能会早于对logging模块配置的时间,启用此选项可能导致config之前实例的logger没有输出操作。

propogate

可以根据需要自行编写logger,python logging logger采用包名完全限定名的全部或部分匹配,如果当前包名没有指定的logger,怎会寻找上一级包名对应的logger。logger配置上有一个propogate参数可以控制logEntry是否继续向下传送到上级包对应的logger,直至root。

使用

import logging
from log import setup_logging

logger = logging.getLogger(__name__)

def main():
    setup_logging()
    logger.info('hello world')

命令行调试

python -c "from log import setup_logging; setup_logging(); import logging; logger = logging.getLogger(); logger.info('abc'); logger.error('error')" >info.txt 2>err.txt

参考资料

https://docs.python.org/3/library/logging.config.html#module-logging.config