Intuition

日志记载是盯梢和记载应用程序中产生的要害事件的过程,用于检查、调试等。它们比print句子更强壮,因为它们允许将特定的信息片段发送到具有自界说功用的特定方位格局化、共享接口等。这使得日志记载成为能够从应用程序的内部流程中发现有洞察力的信息的要害支持者。

成分

有几个整体概念需要注意:

  • Logger: 从应用程序发出日志音讯。
  • Handler:将日志记载发送到特定方位。
  • Formatter: 格局化和款式化日志记载。

日志记载还有很多,例如过滤器、反常日志记载等,但这些基础知识将使能够为应用程序做需要的全部。

等级

在创立专门的装备记载器之前,让看看运用根本装备记载的音讯是什么样子的。

import logging
import sys
# Create super basic logger
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
# Logging levels (from lowest to highest priority)
logging.debug("Used for debugging your code.")
logging.info("Informative messages from your code.")
logging.warning("Everything works but there is something to be aware of.")
logging.error("There's been a mistake with the process.")
logging.critical("There is something terribly wrong and process may terminate.")
DEBUG:root:Used for debugging your code.
INFO:root:Informative messages from your code.
WARNING:root:Everything works but there is something to be aware of.
ERROR:root:There's been a mistake with the process.
CRITICAL:root:There is something terribly wrong and process may terminate.

这些是日志记载的根本等级,其间DEBUG优先级最低,优先级CRITICAL最高。界说了记载器,basicConfig用于将日志音讯发送到标准输出(即终端控制台),但也能够写入任何其他流乃至文件。还将日志界说为对从 level 开端的日志音讯灵敏DEBUG。这意味着一切记载的音讯都将显现,因为DEBUG它是最低等级。假如设置了 level ,ERROR那么只会显现日志音讯。

import logging
import sys
# Create super basic logger
logging.basicConfig(stream=sys.stdout, level=logging.ERROR)
# Logging levels (from lowest to highest priority)
logging.debug("Used for debugging your code.")
logging.info("Informative messages from your code.")
logging.warning("Everything works but there is something to be aware of.")
logging.error("There's been a mistake with the process.")
logging.critical("There is something terribly wrong and process may terminate.")
ERROR:root:There's been a mistake with the process.
CRITICAL:root:There is something terribly wrong and process may terminate.

装备

首先,将在config.py脚本中设置日志的方位:

# config/config.py
LOGS_DIR = Path(BASE_DIR, "logs")
LOGS_DIR.mkdir(parents=True, exist_ok=True)

接下来,将为应用程序装备记载器:

# config/config.py
import logging
import sys
logging_config = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "minimal": {"format": "%(message)s"},
        "detailed": {
            "format": "%(levelname)s %(asctime)s [%(name)s:%(filename)s:%(funcName)s:%(lineno)d]\n%(message)s\n"
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "stream": sys.stdout,
            "formatter": "minimal",
            "level": logging.DEBUG,
        },
        "info": {
            "class": "logging.handlers.RotatingFileHandler",
            "filename": Path(LOGS_DIR, "info.log"),
            "maxBytes": 10485760,  # 1 MB
            "backupCount": 10,
            "formatter": "detailed",
            "level": logging.INFO,
        },
        "error": {
            "class": "logging.handlers.RotatingFileHandler",
            "filename": Path(LOGS_DIR, "error.log"),
            "maxBytes": 10485760,  # 1 MB
            "backupCount": 10,
            "formatter": "detailed",
            "level": logging.ERROR,
        },
    },
    "root": {
        "handlers": ["console", "info", "error"],
        "level": logging.INFO,
        "propagate": True,
    },
}
  1. [Lines 6-11]:界说两个不同的Formatters(确认日志音讯的格局和款式),minimal 和 detailed,它们运用各种LogRecord 特点为日志音讯创立格局化模板。
  2. [Lines 12-35]:界说不同的处理程序(有关发送日志音讯的方位的详细信息):

    • console:将日志音讯(运用minimal格局化程序)发送到stdout高于等级的音讯流DEBUG(即一切记载的音讯)。
    • info:将日志音讯(运用detailed格局化程序)发送到logs/info.log(一个能够到达的文件,1 MB将备份10它的最新版别)以取得等级以上的音讯INFO
    • error:将日志音讯(运用detailed格局化程序)发送到logs/error.log(一个能够到达的文件,1 MB将备份10它的最新版别)以取得等级以上的音讯ERROR
  3. [Lines 36-40]:将不同的处理程序附加到根Logger。

选择运用字典来装备记载器,但还有其他办法,例如 Python 脚本、装备文件等。单击下面的不同选项以打开并查看各自的实现。

Python 脚本

import logging
from rich.logging import RichHandler
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
console_handler = RichHandler(markup=True)
console_handler.setLevel(logging.DEBUG)
info_handler = logging.handlers.RotatingFileHandler(
    filename=Path(LOGS_DIR, "info.log"),
    maxBytes=10485760,  # 1 MB
    backupCount=10,
)
info_handler.setLevel(logging.INFO)
error_handler = logging.handlers.RotatingFileHandler(
    filename=Path(LOGS_DIR, "error.log"),
    maxBytes=10485760,  # 1 MB
    backupCount=10,
)
error_handler.setLevel(logging.ERROR)
minimal_formatter = logging.Formatter(fmt="%(message)s")
detailed_formatter = logging.Formatter(
    fmt="%(levelname)s %(asctime)s [%(name)s:%(filename)s:%(funcName)s:%(lineno)d]\n%(message)s\n"
)
console_handler.setFormatter(fmt=minimal_formatter)
info_handler.setFormatter(fmt=detailed_formatter)
error_handler.setFormatter(fmt=detailed_formatter)
logger.addHandler(hdlr=console_handler)
logger.addHandler(hdlr=info_handler)
logger.addHandler(hdlr=error_handler)
import logging
from rich.logging import RichHandler
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
console_handler = RichHandler(markup=True)
console_handler.setLevel(logging.DEBUG)
info_handler = logging.handlers.RotatingFileHandler(
    filename=Path(LOGS_DIR, "info.log"),
    maxBytes=10485760,  # 1 MB
    backupCount=10,
)
info_handler.setLevel(logging.INFO)
error_handler = logging.handlers.RotatingFileHandler(
    filename=Path(LOGS_DIR, "error.log"),
    maxBytes=10485760,  # 1 MB
    backupCount=10,
)
error_handler.setLevel(logging.ERROR)
minimal_formatter = logging.Formatter(fmt="%(message)s")
detailed_formatter = logging.Formatter(
    fmt="%(levelname)s %(asctime)s [%(name)s:%(filename)s:%(funcName)s:%(lineno)d]\n%(message)s\n"
)
console_handler.setFormatter(fmt=minimal_formatter)
info_handler.setFormatter(fmt=detailed_formatter)
error_handler.setFormatter(fmt=detailed_formatter)
logger.addHandler(hdlr=console_handler)
logger.addHandler(hdlr=info_handler)
logger.addHandler(hdlr=error_handler)
  1. 把它放在一个logging.config文件中:

    [formatters]
    keys=minimal,detailed
    [formatter_minimal]
    format=%(message)s
    [formatter_detailed]
    format=
        %(levelname)s %(asctime)s [%(name)s:%(filename)s:%(funcName)s:%(lineno)d]
        %(message)s
    [handlers]
    keys=console,info,error
    [handler_console]
    class=StreamHandler
    level=DEBUG
    formatter=minimal
    args=(sys.stdout,)
    [handler_info]
    class=handlers.RotatingFileHandler
    level=INFO
    formatter=detailed
    backupCount=10
    maxBytes=10485760
    args=("logs/info.log",)
    [handler_error]
    class=handlers.RotatingFileHandler
    level=ERROR
    formatter=detailed
    backupCount=10
    maxBytes=10485760
    args=("logs/error.log",)
    [loggers]
    keys=root
    [logger_root]
    level=INFO
    handlers=console,info,error
    
  2. 把它放在你的 Python 脚本中

    import logging
    import logging.config
    from rich.logging import RichHandler
    # Use config file to initialize logger
    logging.config.fileConfig(Path(CONFIG_DIR, "logging.config"))
    logger = logging.getLogger()
    logger.handlers[0] = RichHandler(markup=True)  # set rich handler
    

能够像这样加载记载器装备字典:

# config/config.py
from rich.logging import RichHandler
logging.config.dictConfig(logging_config)
logger = logging.getLogger()
logger.handlers[0] = RichHandler(markup=True)  # pretty formatting
# Sample messages (note that we use configured `logger` now)
logger.debug("Used for debugging your code.")
logger.info("Informative messages from your code.")
logger.warning("Everything works but there is something to be aware of.")
logger.error("There's been a mistake with the process.")
logger.critical("There is something terribly wrong and process may terminate.")
DEBUG    Used for debugging your code.                                 config.py:71
INFO     Informative messages from your code.                          config.py:72
WARNING  Everything works but there is something to be aware of.       config.py:73
ERROR    There's been a mistake with the process.                      config.py:74
CRITICAL There is something terribly wrong and process may terminate.  config.py:75

运用RichHandler作为console处理程序来为日志音讯获取漂亮的格局。这不是预装置的库,因而需要装置并增加到requirements.txt

pip install rich==12.4.4

# Add to requirements.txt rich==12.4.4

记载的音讯存储在日志目录中的相应文件中:

logs/
    ├── info.log
    └── error.log

因为界说了详细的格局化程序,因而会看到如下信息丰厚的日志音讯:

应用

在项目中,能够将一切打印句子替换为日志句子:

print("✅ Saved data!")

──── 变成:────

from config.config import logger
logger.info("✅ Saved data!")

一切的日志音讯都在INFO等级上,但在开发过程中可能不得不运用等级,假如系统以意外的方法运行DEBUG,还会增加一些ERROR或日志音讯。CRITICAL

  • what:记载您希望从应用程序中显现的一切必要详细信息,这些详细信息将在开发期间之后的回顾性检查中有用。

  • 其间:最佳做法是不要将模块化函数与日志句子混为一谈。相反,应该在小函数之外和更大的作业流程中记载音讯。例如,除了main.pytrain.py文件之外,任何脚本中都没有日志音讯。这是因为这些脚本运用了其他脚本(data.py、evaluate.py 等)中界说的较小函数。假如觉得需要在其他功用中登录,那么它通常标明该功用需要进一步分化。

Elastic stack(以前称为 ELK stack)是生产级日志记载的常用选项。它结合了Elasticsearch(分布式搜索引擎)、Logstash(吸取管道)和Kibana(可定制可视化)的特性。也能够简单地将日志上传到云博客存储(例如 S3、谷歌云存储等)。


本文主体源自以下链接:

@article{madewithml,
    author       = {Goku Mohandas},
    title        = { Made With ML },
    howpublished = {\url{https://madewithml.com/}},
    year         = {2022}
}

本文由mdnice多渠道发布