python 日志管理

文章目录

  • 1. 日志等级
  • 2. logging(标准库)
    • 2.1. 基础使用
    • 2.2. 模块介绍
      • 2.2.1. Logger
      • 2.2.2. Handlers 日志处理器
      • 2.2.3. Formatter 格式
    • 2.3. 实例1
    • 2.4. 实例2 多线程
  • 3. loguru

1. 日志等级

以下是常见的日志级别,从高到低排列,共8种:
OFF :关闭所有日志记录。
FATAL :记录严重错误事件,这些事件可能导致程序中断。
ERROR :记录错误事件,但不会导致程序中断。
WARN :记录潜在有害的情况。
INFO :记录一般信息,用于描述程序运行过程中的关键事件。
DEBUG :记录详细的调试信息,用于诊断问题。
TRACE :记录最详细的调试信息,用于跟踪程序执行过程。
ALL : 最低等级的,打开所有日志记录。

2. logging(标准库)

logging 模块是 Python 内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等。

标注库 logging:
https://docs.python.org/zh-tw/3.10/howto/logging.html
其他参考:
https://www.jb51.net/python/2951838fb.htm
多线程下使用:
https://www.yisu.com/ask/39360259.html
多进程下使用:
https://cloud.tencent.com/developer/article/1114858

2.1. 基础使用

import logging

# 配置日志等级为INFO
logging.basicConfig(level=logging.INFO)

logging.debug("debug log")
logging.info("info log")
logging.warning("warning log")
logging.error("error log")
logging.critical("critical log")

运行结果:

INFO:root:info log
WARNING:root:warning log
ERROR:root:error log
CRITICAL:root:critical log

2.2. 模块介绍

logging 模块中定义的函数和类为应用程序和库的开发实现了一个灵活的事件日志系统。Python logging 的配置由四个部分组成:Logger、Handlers、Formatter、Filter。

2.2.1. Logger

创建一个logger(日志记录器) 对象。
Logger 对象有三重任务。
首先,它们向应用程序代码公开了几种方法,以便应用程序可以在运行时记录消息。
其次,记录器对象根据严重性(默认过滤工具)或过滤器对象确定要处理的日志消息。
第三,记录器对象将相关的日志消息传递给所有感兴趣的日志处理器。

记录器对象上使用最广泛的方法分为两类:配置和消息发送。
配置方法:
Logger.setLevel() 指定记录器将处理的最低严重性日志消息,其中 debug 是最低内置严重性级别, critical 是最高内置严重性级别。 例如,如果严重性级别为 INFO ,则记录器将仅处理 INFO 、 WARNING 、 ERROR 和 CRITICAL 消息,并将忽略 DEBUG 消息。
Logger.addHandler() 和 Logger.removeHandler() 从记录器对象中添加和删除处理器对象。处理器在以下内容中有更详细的介绍 处理器 。
Logger.addFilter() 和 Logger.removeFilter() 可以添加或移除记录器对象中的过滤器。 过滤器对象 包含更多的过滤器细节。

创建日志消息:
Logger.debug()Logger.info()Logger.warning()Logger.error()Logger.critical() 都创建日志记录,包含消息和与其各自方法名称对应的级别。该消息实际上是一个格式化字符串,它可能包含标题字符串替换语法 %s 、 %d 、 %f 等等。其余参数是与消息中的替换字段对应的对象列表。关于 **kwargs ,日志记录方法只关注 exc_info 的关键字,并用它来确定是否记录异常信息。
Logger.exception() 创建与 Logger.error() 相似的日志信息。 不同之处是, Logger.exception() 同时还记录当前的堆栈追踪。仅从异常处理程序调用此方法。
Logger.log() 将日志级别作为显式参数。对于记录消息而言,这比使用上面列出的日志级别便利方法更加冗长,但这是使用自定义日志级别的方法。

getLogger() 返回对具有指定名称的记录器实例的引用(如果已提供),或者如果没有则返回 root 。名称是以句点分隔的层次结构。多次调用 getLogger() 具有相同的名称将返回对同一记录器对象的引用。在分层列表中较低的记录器是列表中较高的记录器的子项。例如,给定一个名为 foo 的记录器,名称为 foo.bar 、 foo.bar.baz 和 foo.bam 的记录器都是 foo 子项。

2.2.2. Handlers 日志处理器

决定把日志发到哪里。

常用的是:
  StreamHandler --> 输出控制平台
  FileHandler --> 输出到文件

Handler 对象负责将适当的日志消息(基于日志消息的严重性)分派给处理器的指定目标。 Logger 对象可以使用 addHandler() 方法向自己添加零个或多个处理器对象。作为示例场景,应用程序可能希望将所有日志消息发送到日志文件,将错误或更高的所有日志消息发送到标准输出,以及将所有关键消息发送至一个邮件地址。 此方案需要三个单独的处理器,其中每个处理器负责将特定严重性的消息发送到特定位置。
标准库包含很多处理器类型(参见 有用的处理器 );教程主要使用 StreamHandlerFileHandler
处理器中很少有方法可供应用程序开发人员使用。使用内置处理器对象(即不创建自定义处理器)的应用程序开发人员能用到的仅有以下配置方法:

setLevel() 方法,就像在记录器对象中一样,指定将被分派到适当目标的最低严重性。为什么有两个 setLevel() 方法?记录器中设置的级别确定将传递给其处理器的消息的严重性。每个处理器中设置的级别确定该处理器将发送哪些消息。
setFormatter() 选择一个该处理器使用的 Formatter 对象。
addFilter() 和 removeFilter() 分别在处理器上配置和取消配置过滤器对象。
应用程序代码不应直接实例化并使用 Handler 的实例。 相反, Handler 类是一个基类,它定义了所有处理器应该具有的接口,并建立了子类可以使用(或覆盖)的一些默认行为。

作为 Handler 基类的补充,提供了很多有用的子类:
StreamHandler 实例发送消息到流(类似文件对象)。
FileHandler 实例将消息发送到硬盘文件。
BaseRotatingHandler 是轮换日志文件的处理器的基类。它并不应该直接实例化。而应该使用 RotatingFileHandler 或 TimedRotatingFileHandler 代替它。
RotatingFileHandler 实例将消息发送到硬盘文件,支持最大日志文件大小和日志文件轮换。
TimedRotatingFileHandler 实例将消息发送到硬盘文件,以特定的时间间隔轮换日志文件。
SocketHandler 实例将消息发送到 TCP/IP 套接字。从 3.4 开始,也支持 Unix 域套接字。
DatagramHandler 实例将消息发送到 UDP 套接字。从 3.4 开始,也支持 Unix 域套接字。
SMTPHandler 实例将消息发送到指定的电子邮件地址。
SysLogHandler 实例将消息发送到 Unix syslog 守护程序,可能在远程计算机上。
NTEventLogHandler 实例将消息发送到 Windows NT/2000/XP 事件日志。
MemoryHandler 实例将消息发送到内存中的缓冲区,只要满足特定条件,缓冲区就会刷新。
HTTPHandler 实例使用 GET 或 POST 方法将消息发送到 HTTP 服务器。
WatchedFileHandler 实例会监视他们要写入日志的文件。如果文件发生更改,则会关闭该文件并使用文件名重新打开。此处理器仅在类 Unix 系统上有用; Windows 不支持依赖的基础机制。
QueueHandler 实例将消息发送到队列,例如在 queue 或 multiprocessing 模块中实现的队列。
NullHandler 实例对错误消息不执行任何操作。它们由想要使用日志记录的库开发人员使用,但是想要避免如果库用户没有配置日志记录,则显示 ‘No handlers could be found for logger XXX’ 消息的情况。更多有关信息,请参阅 为库配置日志 。

2.2.3. Formatter 格式

Formatter 用来规定 Log record 文本的格式,其使用 python formatting string 来规定具体格式。在默认情况下,logging 模块的输出格式如下:

%(name)s        : 日志记录器的名称
%(levelno)s     : 打印日志级别的数值。
%(levelname)s   : 打印日志级别的名称。
%(pathname)s    : 打印当前执行程序的路径,其实就是sys.argv[0]%(filename)s    : 打印当前执行程序名。
%(funcName)s    : 打印日志的当前函数。
%(lineno)d      : 打印日志的当前行号。
%(asctime)s     : 打印日志的时间。
%(thread)d      : 打印线程ID。
%(threadName)s  : 打印线程名称。
%(process)d     : 打印进程ID。
%(processName)s : 打印线程名称。
%(module)s      : 打印模块名称。
%(message)s     : 打印日志信息。

扩展:

# -8 左对齐
FMT_ = '%(asctime)s %(levelname)-8s | %(filename)s:%(lineno)d - %(message)s'

设置 Formatter 主要包括两种方式:
一种是通过 Formatter 类构建 Formatter 实例,并将其绑定到特定的 handler 上;
一种是通过 logging.basicConfig 设置:

# 通过 logging.basicConfig 设置日志配置格式。
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(funcName)s - %(message)s',
    datefmt='%Y/%m/%d %H:%M:%S',
    style='%' # '%', ‘{‘ or ‘$’
)

# 通过 Formatter 类构建 Formatter 实例,并将其绑定到特定的 handler 上。
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s', 
    datefmt='%Y/%m/%d %H:%M:%S', 
    style='%'
)
formatter.converter = time.localtime()
formatter.converter = time.gmtime()

2.3. 实例1

import logging.handlers
import sys
import os
import logging
from time import strftime
import threading

FLODER_ = './logs/'
LOGFILE_ = 'log'
"""
%(name)s        : 日志记录器的名称
%(levelno)s     : 打印日志级别的数值。
%(levelname)s   : 打印日志级别的名称。
%(pathname)s    : 打印当前执行程序的路径,其实就是sys.argv[0]。
%(filename)s    : 打印当前执行程序名。
%(funcName)s    : 打印日志的当前函数。
%(lineno)d      : 打印日志的当前行号。
%(asctime)s     : 打印日志的时间。
%(thread)d      : 打印线程ID。
%(threadName)s  : 打印线程名称。
%(process)d     : 打印进程ID。
%(processName)s : 打印线程名称。
%(module)s      : 打印模块名称。
%(message)s     : 打印日志信息。
"""
# FMT_ = '%(asctime)s %(levelname)8s | %(process)d %(name)s | %(filename)s:%(lineno)d %(message)s'
FMT_ = '%(asctime)s %(levelname)-8s | %(filename)s:%(lineno)d - %(message)s'
DATEFMT_ = '%Y-%m-%d %H:%M:%S'


# 配置文件输出
ENCODING_ = 'utf-8'
### 根据时间切割日志
# 定义默认日志切割的时间单位,比如 'S'(秒)、'M'(分)、'H'(小时)、'D'(天)等
WHEN_ = "D"
# 定义默认日志文件切割的时间间隔,例如当 when='H' 且 interval=1 时,表示每隔一个小时进行一次切割,并生成一个新的日志文件
INTERVAL_ = 1
# 定义默认保留旧日志文件的个数(如果超过这个数量,则会自动删除最早的日志文件),默认值为 0,表示不自动删除旧日志文件
BACKUPCOUNT_ = 0
### 根据大小切割日志
# 定义默认日志文件最大字节数(2M)
LOG_MAX_BYTES_ = 2 * 1024 * 1024
# LOG_MAX_BYTES_ = 1024
# 定义默认日志文件备份个数
BACKUPCOUNT_ = 5

class Loggings(object):
    __instance = None
    __instance_lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            # 输出日志路径
            # PATH = os.path.abspath('.') + '/logs/'
            with Loggings.__instance_lock:
                cls.__instance = logging.getLogger()
                _formatter = logging.Formatter(fmt=FMT_, datefmt=DATEFMT_)
                
                _filename = '{0}{1}_{2}.txt'.format(FLODER_, LOGFILE_, strftime("%Y-%m-%d"))
                _file_handler = logging.FileHandler(_filename, encoding=ENCODING_)
                _file_handler.setFormatter(_formatter)
                cls.__instance.addHandler(_file_handler)

                _console_handler = logging.StreamHandler(sys.stdout)
                _console_handler.setFormatter(_formatter)
                cls.__instance.addHandler(_console_handler)
                # 设置日志的默认级别
                cls.__instance.setLevel(logging.DEBUG)

        return cls.__instance

def init_logging():
    # 设置日志格式#和时间格式
    # ansi_format = '\033[95m%(levelname)s:\033[0m%(message)s'  # 95是深紫色背景文字,0是重置颜色
    # formatter = logging.Formatter(ansi_format)

    # 拼接日志文件完整路径
    log_filename = os.path.join(FLODER_, LOGFILE_)
    # # 使用绝对路径
    # # 获取当前脚本所在的目录路径。该方法获取不正确时,使用方法二:os.path.realpath(sys.argv[0])
    # root_path = os.path.dirname(os.path.abspath(sys.argv[0]))
    # # 如果指定路径不存在,则尝试创建路径
    # if not os.path.exists(os.path.join(root_path, FLODER_)):
    #     os.makedirs(os.path.join(root_path, FLODER_))
    # 使用相对路径
    if not os.path.exists(FLODER_):
        os.makedirs(FLODER_)

    # Create a log format using Log Record attributes
    # 输出日志路径
    # PATH = os.path.abspath('.') + '/logs/'
    _logger = logging.getLogger()
    _formatter = logging.Formatter(fmt=FMT_, datefmt=DATEFMT_)
    
    _filename = '{0}.txt'.format(log_filename)
    # _file_handler = logging.FileHandler(_filename, encoding=ENCODING_)
    
    ### 根据时间切割日志
    _file_handler = logging.handlers.TimedRotatingFileHandler(
                                            filename=_filename,
                                            when=WHEN_,
                                            interval=INTERVAL_,
                                            backupCount=BACKUPCOUNT_,
                                            encoding=ENCODING_)  # 创建 TimedRotatingFileHandler 实例,即将日志输出到文件的处理器
    
    # ### 根据大小切割日志
    # _file_handler = logging.handlers.RotatingFileHandler(
    #                                         filename=_filename,
    #                                         maxBytes=LOG_MAX_BYTES_,
    #                                         backupCount=BACKUPCOUNT_,
    #                                         encoding=ENCODING_)  # 创建 RotatingFileHandler 实例,即将日志输出到文件的处理器

    _console_handler = logging.StreamHandler(sys.stdout)

    _file_handler.setFormatter(_formatter)
    _console_handler.setFormatter(_formatter)

    _logger.addHandler(_file_handler)
    _logger.addHandler(_console_handler)
    # 设置日志的默认级别
    _logger.setLevel(logging.DEBUG)

if __name__ == "__main__":
    # # from utils.loggings import Loggings
    # my_logg = Loggings()
    
    # my_logg.debug("debug log")
    # my_logg.info("info log")
    # my_logg.warning("warning log")
    # my_logg.error("error log")
    # my_logg.critical("critical log")

    init_logging()
    logging.debug("debug log")
    logging.info("info log")
    logging.warning("warning log")
    logging.error("error log")
    logging.critical("critical log")

运行结果:

2025-04-10 16:41:49 DEBUG    | loggings.py:137 - debug log
2025-04-10 16:41:49 INFO     | loggings.py:138 - info log
2025-04-10 16:41:49 WARNING  | loggings.py:139 - warning log
2025-04-10 16:41:49 ERROR    | loggings.py:140 - error log
2025-04-10 16:41:49 CRITICAL | loggings.py:141 - critical log

2.4. 实例2 多线程

在多线程环境下,使用 Python 的 logging 模块可以很方便地实现多线程的日志记录。logging 模块提供了线程安全的日志记录功能,可以在多个线程同时写日志时保证线程安全。

import logging
import threading

def worker(num):
    logging.info('Worker %d started', num)
    logging.warning('Worker %d is doing some work', num)
    logging.error('Worker %d encountered an error', num)
    logging.info('Worker %d finished', num)

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

    for i in range(5):
        t = threading.Thread(target=worker, args=(i,))
        t.start()

在上面的示例中,我们创建了5个线程,每个线程会调用worker函数,在函数中记录了不同级别的日志。
当我们运行这段代码时,可以看到多个线程同时记录日志,而 logging 模块会确保线程安全地记录日志,避免日志消息相互干扰。
总的来说,使用 Python 的 logging 模块在多线程环境下记录日志是一个简单且高效的方式,可以方便地对多个线程的日志进行管理和记录。

3. loguru

参考:
https://www.cnblogs.com/struggleMan/p/17510494.html

你可能感兴趣的:(python,python,服务器)