Python 搞懂Logger模块让老板刮目相看

一个简单的示例:

ChatGPT
Logger是Python标准库中logging模块提供的一种日志记录工具。它允许你在应用程序中记录不同级别的日志消息,以便于调试、追踪和分析应用程序的运行。使用Logger有助于管理和组织日志输出,以及提供可配置的日志记录。

以下是Logger的主要组成部分:

Logger(记录器):是主要的接口,负责发送日志消息。应用程序通常会创建一个或多个Logger对象,每个Logger对象负责一组相关的功能或模块。

Handler(处理器):决定将日志消息发送到何处。Handler可以将日志消息输出到控制台、文件、网络等不同的目标。

Formatter(格式化器):定义日志消息的输出格式。Formatter可以格式化消息的时间、级别、模块名等信息,使日志信息更易于阅读和分析。

Filter(过滤器):允许更细粒度地控制哪些日志消息应该被处理。通过使用过滤器,可以根据需要选择性地记录或忽略某些消息。

下面是一个简单的例子,演示如何使用Logger记录日志:

import logging

# 创建Logger对象  名字为  my_logger
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG)

# 创建文件处理器,并设置级别为DEBUG  输出文件名字为:my_log.log
file_handler = logging.FileHandler('my_log.log')
file_handler.setLevel(logging.DEBUG)

# 创建控制台处理器,并设置级别为INFO
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# 创建日志消息的格式化器
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')

# 将格式化器添加到处理器 
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# 将处理器添加到记录器
logger.addHandler(file_handler)
logger.addHandler(console_handler)

# 记录不同级别的日志消息
logger.debug('This is a debug message')
logger.info('This is an info message')
logger.warning('This is a warning message')
logger.error('This is an error message')
logger.critical('This is a critical message')

运行效果

Python 搞懂Logger模块让老板刮目相看_第1张图片

同时还可以发现  控制台终端输出的消息  比文件中 少了一条DEBUG消息 ,这是因为 终端中设置的是INFO级别(日志消息只有>=INFO才记录),文件时DEBUG级别(日志消息只有>=DEBUG才记录), 而日志的级别从低到高分别是:DEBUG->INFO->WARNING->ERROR->CRITICAL。

对应的解释如下:

  1. DEBUG: 用于详细的调试信息。在开发和调试阶段使用,通常不应在生产环境中启用。

  2. INFO: 提供程序正常运行时的信息。适用于生产环境,用于确认程序是否按照预期工作。

  3. WARNING: 表示可能的问题,但不会影响程序的正常运行。需要注意并进行检查,以确保程序的稳定性。

  4. ERROR: 指示更严重的问题,可能导致某些功能的失败。需要及时关注和修复。

  5. CRITICAL: 表示严重的错误,可能导致程序无法继续运行。通常需要立即解决。

  6. 在给定的日志配置中,如果设置了级别为INFO,那么 INFO、WARNING、ERROR、CRITICAL级别的日志消息都会被记录,而DEBUG级别的消息将被忽略

同时还有一些没用的小知识:

logging.basicConfig函数各参数:

属性名称 格式 说明
filename 指定日志文件名
filemode 指定文件的打开模式,'w'或者'a';
format 格式器 指定输出的格式和内容
datefmt 指定时间格式,同time.strftime()
level 设置日志级别,默认为logging.WARNNING
stream 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略

格式器format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')

可能会用到的一些其他参数名字: 用法如上

Python 搞懂Logger模块让老板刮目相看_第2张图片

日志处理器 :

console_handler = logging.StreamHandler()  #创建日志处理器 logger.addHandler(console_handler) #添加日志处理器

还有一些其他的没用的小知识 :其他日志处理器如下

  • StreamHandler:logging.StreamHandler;日志输出到流,可以是sys.stderr,sys.stdout或者文件
  • FileHandler:logging.FileHandler;日志输出到文件
  • BaseRotatingHandler:logging.handlers.BaseRotatingHandler;基本的日志回滚方式
  • RotatingHandler:logging.handlers.RotatingHandler;日志回滚方式,支持日志文件最大数量和日志文件回滚
  • TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日志回滚方式,在一定时间区域内回滚日志文件
  • SocketHandler:logging.handlers.SocketHandler;远程输出日志到TCP/IP sockets
  • DatagramHandler:logging.handlers.DatagramHandler;远程输出日志到UDP sockets
  • SMTPHandler:logging.handlers.SMTPHandler;远程输出日志到邮件地址
  • SysLogHandler:logging.handlers.SysLogHandler;日志输出到syslog
  • NTEventLogHandler:logging.handlers.NTEventLogHandler;远程输出日志到Windows NT/2000/XP的事件日志
  • MemoryHandler:logging.handlers.MemoryHandler;日志输出到内存中的指定buffer
  • HTTPHandler:logging.handlers.HTTPHandler;通过"GET"或者"POST"远程输出到HTTP服务器

日志回滚

举个栗子:

import logging
from logging.handlers import RotatingFileHandler

# 配置日志
logger = logging.getLogger("example_logger")
logger.setLevel(logging.DEBUG)

# 创建 RotatingFileHandler 处理器,设置日志文件名和最大文件大小 backupCout指定要保留的备份日
#志文件的数量
handler = RotatingFileHandler("example.log", maxBytes=1, backupCount=3)

# 配置日志格式
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

# 将处理器添加到 logger
logger.addHandler(handler)

def simulate_logging():
    # 模拟写入日志
    for i in range(10):
        logger.debug(f"This is log entry {i}")

if __name__ == "__main__":
    simulate_logging()

结果很明显了:

Python 搞懂Logger模块让老板刮目相看_第3张图片

在上面的例子中,我们使用了 RotatingFileHandler 处理器,并设置了最大文件大小为 1 字节,备份文件的数量为 3。这意味着当日志文件大小达到 1 字节时,会将当前日志文件切分为一个备份文件,并保留最多 3 个备份文件。

你可以根据实际需求调整 maxBytesbackupCount 参数。当日志文件大小达到 maxBytes 时,会触发日志回滚。

还有篇文章不错一定要看:https://www.cnblogs.com/yxh168/articles/11855581.html#:~:text=backupCount,%E6%98%AF%E4%BF%9D%E7%95%99%E6%97%A5%E5%BF%97%E4%B8%AA%E6%95%B0.%E9%BB%98%E8%AE%A4%E7%9A%840%E6%98%AF%E4%B8%8D%E4%BC%9A%E8%87%AA%E5%8A%A8%E5%88%A0%E9%99%A4%E6%8E%89%E6%97%A5%E5%BF%97%2C%E8%8B%A5%E8%AE%BE10%2C%E5%88%99%E5%9C%A8%E6%96%87%E4%BB%B6%E7%9A%84%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B%E4%B8%AD%E5%BA%93%E4%BC%9A%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E6%9C%89%E8%B6%85%E8%BF%87%E8%BF%99%E4%B8%AA10%2C%E8%8B%A5%E8%B6%85%E8%BF%87%2C%E5%88%99%E4%BC%9A%E4%BB%8E%E6%9C%80%E5%85%88%E5%88%9B%E5%BB%BA%E7%9A%84%E5%BC%80%E5%A7%8B%E5%88%A0%E9%99%A4icon-default.png?t=N7T8https://www.cnblogs.com/yxh168/articles/11855581.html#:~:text=backupCount,%E6%98%AF%E4%BF%9D%E7%95%99%E6%97%A5%E5%BF%97%E4%B8%AA%E6%95%B0.%E9%BB%98%E8%AE%A4%E7%9A%840%E6%98%AF%E4%B8%8D%E4%BC%9A%E8%87%AA%E5%8A%A8%E5%88%A0%E9%99%A4%E6%8E%89%E6%97%A5%E5%BF%97%2C%E8%8B%A5%E8%AE%BE10%2C%E5%88%99%E5%9C%A8%E6%96%87%E4%BB%B6%E7%9A%84%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B%E4%B8%AD%E5%BA%93%E4%BC%9A%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E6%9C%89%E8%B6%85%E8%BF%87%E8%BF%99%E4%B8%AA10%2C%E8%8B%A5%E8%B6%85%E8%BF%87%2C%E5%88%99%E4%BC%9A%E4%BB%8E%E6%9C%80%E5%85%88%E5%88%9B%E5%BB%BA%E7%9A%84%E5%BC%80%E5%A7%8B%E5%88%A0%E9%99%A4

捕获traceback,输出系统错误信息

import logging
import traceback

# 配置日志
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s-%(levelname)s-%(message)s')


def divide_numbers(a, b):
    try:
        result = a / b
        return result
    except Exception as e:
        # 捕获异常并记录 traceback 到日志 
        logging.error("An error occurred: %s", e, exc_info=True) # 错误信息捕获
        return None 


def main():
    logging.info("logger开始")
    # 模拟除法操作,可能引发异常
    result = divide_numbers(10, 0)

    # 如果异常被捕获,程序可以继续执行
    if result is None:
        logging.warning("The result is not available due to a previous error.")

    # 模拟正常情况
    result = divide_numbers(10, 2)
    logging.info("The result is: %s", result)


if __name__ == "__main__":
    main()

结果一目了然了:

Python 搞懂Logger模块让老板刮目相看_第4张图片

多模块的logging

这是更加常见的 因为一个完成的项目应该是包含不同的模块的,我又想针对不同的模块来设置不同的logging,就需要这个了

module.py

import logging
 
# 为模块创建一个日志记录器 
logger = logging.getLogger(__name__) 

# 设置模块的日志级别为DEBUG 
logger.setLevel(logging.DEBUG) 

def print_message(): 
    logger.debug("这是来自模块的调试消息。") 
    logger.info("这是来自模块的信息消息。")

main.py

import logging 
import module # 配置根日志记录器
 
logging.basicConfig(level=logging.INFO, 
                    format='%(asctime)s - %(levelname)s - %(message)s'
                  )         # 为主模块创建一个日志记录器 

logger = logging.getLogger(__name__) 
# logger = logging.getLogger("mainxxxx")    #你也可以自己指定

def main(): 
    logger.info("这是来自主模块的信息消息。") 
    module.print_message() 

if __name__ == "__main__": 
    main()

在这个例子中,主模块main.py的根日志级别设置为INFO,而module.py的日志级别被设置为DEBUG。这意味着,module.py中的DEBUG级别的日志消息将被输出,而在main.py中只有INFO级别及以上的消息才会被输出。

这样,当你运行main.py时,你将看到类似以下的输出:

2023-12-06 00:00:00,000 - INFO - 这是来自主模块的信息消息。

2023-12-06 00:00:00,000 - DEBUG - 这是来自模块的调试消息。

2023-12-06 00:00:00,000 - INFO - 这是来自模块的信息消息。

注意:

在Python的logging模块中,basicConfig函数是用于配置默认的根日志记录器(root logger)的函数,而不是为特定的logger配置参数。当你在一个模块中使用basicConfig时,它会配置根日志记录器,而不是模块专属的logger。

如果你想为特定模块配置独立的logger,通常会使用getLogger来创建一个logger实例,然后对该实例进行配置。这样做的好处是,你可以独立地控制每个logger的配置,而不影响其他模块或根记录器。

有一个知识点:

当你在不同的模块中使用getLogger(__name__)时,每个模块都会得到一个独立的logger实例,其名称基于模块的名称。这样,你可以轻松地在日志中识别消息来自哪个模块。

例如,如果你有两个模块mainmodule,它们都使用getLogger(__name__)创建logger,那么它们的logger名称分别是mainmodule。当你在日志中看到来自main模块的消息时,你知道这些消息来自main模块。

对于 :

Logger从来不直接实例化,经常通过logging模块级方法(Module-Level Function)logging.getLogger(name)来获得,其中如果name不给定就用root。名字是以点号分割的命名方式命名的(a.b.c)。对同一个名字的多个调用logging.getLogger()方法会返回同一个logger对象。这种命名方式里面,后面的loggers是前面logger的子logger,自动继承父loggers的log信息,正因为此,没有必要把一个应用的所有logger都配置一遍,只要把顶层的logger配置好了,然后子logger根据需要继承就行了。

这句话的理解:Python 搞懂Logger模块让老板刮目相看_第5张图片

Python 搞懂Logger模块让老板刮目相看_第6张图片

通过YAML文件来配置logger

第一个案例:

config.yml

Log:
  log_level: 1
  path_to_log: ./log

log.py

import logging
import os
import sys
from datetime import datetime
from logging import handlers

import yaml


class Logger(object):
    # 日志级别关系映射
    level_relations = {
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR,
        'critical': logging.CRITICAL
    }

    def __init__(self, log_file_path, level='info', when='midnight', backCount=30,
                 fmt='%(asctime)s - %(module)s.cpp[line:%(lineno)d] - %(levelname)s: %(message)s'):
        """
        :param log_file_path: 日志文件路径
        :param level: 日志级别
        :param when: 切割时间间隔:S: 秒, M: 分, H: 小时, D: 天: W0~W6: 星期几, midnight: 每天零点
        :param backCount: 保留的日志文件数量
        :param fmt: 日志格式
        """
        self.logger = logging.getLogger(log_file_path)
        # self.logger.handlers.clear()
        # 是否已创建日志记录程序
        if not self.logger.handlers:
            self.logger.setLevel(self.level_relations.get(level))
            log_formatter = logging.Formatter(fmt)

            # 日志流处理器,将日志输出到屏幕上
            stream_handler = logging.StreamHandler()
            stream_handler.setFormatter(log_formatter)
            self.logger.addHandler(stream_handler)
            # 刷新标准输出缓冲区,确保之前的日志消息被立即输出到屏幕
            sys.stdout.flush()

            # 日志定时循环文件处理器,将日志写入到文件
            file_handler = handlers.TimedRotatingFileHandler(filename=log_file_path, when=when, backupCount=backCount,
                                                             encoding='utf-8')
            file_handler.setFormatter(log_formatter)
            self.logger.addHandler(file_handler)

    def __getattr__(self, name):
        # log.xxx 时,如果在日志级别中,返回对应的日志级别方法
        if name in self.level_relations:
            return getattr(self.logger, name)
        # 返回日志对象的指定属性名的值
        return self.logger.__getattribute__(name)


# 加载配置文件
def get_config(file_name):
    # 读取配置文件
    if len(file_name) == 1:
        file_name = 'config.yml'
    else:
        file_name = file_name[1]  # 自定义配置文件名称
    try:
        with open(file_name, 'r', encoding='utf-8') as config_file:
            cfg = yaml.load(config_file, Loader=yaml.FullLoader)
    except:
        with open(file_name, 'r', encoding='ansi') as config_file:
            cfg = yaml.load(config_file, Loader=yaml.FullLoader)

    # log保存路径,以年月日命名log文件
    log_file_dir = cfg['Log']['path_to_log']
    log_file = log_file_dir + "/" + datetime.now().strftime('%m_%d_%Y.log')

    # 判断log文件夹是否存在,若不存在,创建该目录文件夹
    if not os.path.exists(log_file_dir):
        os.makedirs(log_file_dir)
    # 日志级别
    dict = {"0": "debug", "1": "info", "2": "warning", "3": "error", "4": "critical"}
    # 读取log路径及设置log等级
    log = Logger(log_file, level=dict[str(cfg['Log']['log_level'])])
    return log, cfg


log, cfg = get_config(sys.argv)

第二个案例:(更推荐的)

resources/logging.yml

version: 1  # 配置文件版本,当前为1

formatters:
  simple:  # 定义一个名为 "simple" 的日志格式
    format: '%(asctime)s - %(module)s.cpp[line:%(lineno)d] - %(levelname)s: %(message)s'
    # 详细定义日志记录的格式,包括时间、模块、行号、日志级别和消息

handlers:
  timed_rotating:
    class: logging.handlers.TimedRotatingFileHandler
    # 使用 TimedRotatingFileHandler 类来处理日志
    level: DEBUG  # 设置日志处理器的级别为 DEBUG
    formatter: simple  # 使用上面定义的 "simple" 格式
    when: midnight  # 每天的午夜切分日志文件
    interval: 1  # 间隔为1天
    backupCount: 30  # 保留最多30个备份文件
    filename: ./log/snap-video-service.log  # 指定日志文件路径
    encoding: utf-8  # 设置日志文件的编码为 utf-8

  console_handler:
    class: logging.StreamHandler
    # 使用 StreamHandler 类来将日志输出到控制台
    level: DEBUG  # 设置日志处理器的级别为 DEBUG
    formatter: simple  # 使用上面定义的 "simple" 格式
    stream: ext://sys.stdout  # 输出到标准输出流

loggers:
  app:
    level: DEBUG  # 设置 "app" logger 的级别为 DEBUG
    handlers: [ timed_rotating ]  # 将 "timed_rotating" 处理器添加到 "app" logger
    propagate: no  # 防止日志传播给父 logger

root:
  level: INFO  # 设置根 logger 的级别为 INFO
  handlers: [ console_handler, timed_rotating ]  # 将两个处理器添加到根 logger

config.yml

Log:
  log_level: 0
  path_to_log: ./log #保存本地配置

#其他的配置xxxx
videoSave:
  #推送视频画面的宽,与输入视频流保持一致
  # videoW: 1920
  videoW: 1280
  #推送视频画面的高,与输入视频流保持一致
  # videoH: 1080
  videoH: 720
  #推送rtmp视频流和保存本地视频的帧率
  videoFps: 15
  #是否保存处理后的视频文件,1保存,0不保存
  isSaveVideo: 0
  # 推送rtmp视频流或者保存本地 1:rtmp 0:本地
  rtmpOrLocal: 1
  # url地址
  videoPushUrl: rtmp://localhost:1935/myapp
  #若保存视频文件,填写保存路径
  videoSavePath: ./data
  #若保存视频文件,填写单个视频的最大占存,单位为兆(M)
  videoMaxSize: 500

config.py

# 导入必要的模块
import logging
import logging.config as log_config
import os
import yaml

# 配置全局日志
def global_config(logging_cnf='resources/logging.yml'):
    # 创建 'log' 目录,如果目录已存在则忽略
    os.makedirs('log', exist_ok=True)

    # 读取日志配置文件
    with open(logging_cnf, encoding='utf-8') as f:
        # 解析配置文件内容
        log_cnf = yaml.safe_load(f.read())

        # 配置 logging 模块
        log_config.dictConfig(log_cnf)

# 获取根 logger
logger = logging.getLogger()

# 尝试读取应用程序配置文件
try:
    with open('./config.yml', 'r', encoding='utf-8') as config_file:
        # 解析配置文件内容
        cfg = yaml.load(config_file, Loader=yaml.FullLoader)
except (Exception,) as e:
    # 记录错误日志,指示配置文件读取失败
    logger.error("config.yml配置文件读取失败,请核查")

main.py

import sys

import config
from utils import VideoSave

if __name__ == '__main__':
    if len(sys.argv) < 2:
        config.global_config()
    else:
        config.global_config(sys.argv[1])

值得阅读的文章:

https://www.jb51.net/article/192489.htmicon-default.png?t=N7T8https://www.jb51.net/article/192489.htm

你可能感兴趣的:(for_myself,Python,python)