Python 中的日志模块 logging

type
Post
status
Published
summary
Python 中的日志模块 logging 使用技巧与应用
slug
python-logging
date
Jan 5, 2025
tags
python
logging
category
基础知识
password
icon
URL
Property
Jan 14, 2025 08:34 AM

为什么要用logging而不用print

  • print:
    • 用途:主要用于调试或临时输出信息。
    • 输出:输出到标准输出(通常是控制台)。
    • 缺点
      • 无法区分信息的严重程度。
      • 缺乏灵活性,难以管理输出。
      • 没有统一的管理方式,输出杂乱,难以筛选有用信息。
      • 调试完成后,需要手动删除或注释所有 print 语句。
      • 在高并发场景中性能较差。
  • logging:
    • 用途:专为记录日志设计,适用于生产环境。
    • 优点
      • 提供多种日志级别,区分信息的重要性。
      • 支持灵活的输出位置(控制台、文件、网络等)。
      • 支持格式化输出,方便分析和阅读。
      • 提供日志的时间、文件、行号等上下文信息。
      • 可以通过配置文件或代码集中管理日志输出,便于控制。
      • 支持根据日志级别筛选输出内容。
      • 配置可以轻松更改,不需要修改代码即可切换输出模式或日志级别。
      • 更加高效,支持异步处理、文件轮换等功能,在复杂场景中表现更佳。

快速开始

  • 默认情况下,logging 只会输出 WARNING 及以上级别的日志。
  • 当指定了日志级别,最终只会输出指定级别以上的日志。

basicConfig 模块

快速配置日志系统,适合简单的使用场景。
参数名称
参数说明
参数可选值
参数默认值
参数调用示例
filename
指定日志输出文件的文件名。
字符串(文件路径)
None
logging.basicConfig(filename='app.log')
filemode
指定日志文件的打开模式。
'w'(覆盖写)、'a'(追加写)
'a'
logging.basicConfig(filename='app.log', filemode='w')
datefmt
指定日志中时间字段的格式。
时间格式化字符串(如 '%Y-%m-%d %H:%M:%S'
None
logging.basicConfig(datefmt='%Y-%m-%d %H:%M:%S')
level
设置日志记录的最低级别,低于该级别的日志将被忽略。
logging.DEBUG(10):调试信息 logging.INFO(20):正常运行的信息 logging.WARNING(30):警告信息(默认级别) logging.ERROR(40):错误信息 logging.CRITICAL(50):严重错误信息
logging.WARNING
logging.basicConfig(level=logging.DEBUG)
encoding
指定日志文件的编码格式。
编码字符串(如 'utf-8'
None
logging.basicConfig(filename='app.log', encoding='utf-8')
format
指定日志输出的格式。
格式化字符串(如 '%(asctime)s - %(levelname)s - %(message)s') 常用格式字段: %(asctime)s:日志时间。 %(name)s:日志记录器的名称。 %(levelname)s:日志级别。 %(message)s:日志内容。 %(filename)s:文件名。 %(lineno)d:行号。
默认格式
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s')
stream
指定日志输出的流(如 sys.stdout 或 sys.stderr)。
文件流对象
None
logging.basicConfig(stream=sys.stdout)
handlers
指定日志处理器列表。如果指定了 handlers,则 filename 和 stream 将被忽略。
处理器对象列表(如 [logging.StreamHandler(), logging.FileHandler('app.log')]
None
logging.basicConfig(handlers=[logging.StreamHandler()])
force
如果为 True,则会强制重新配置日志记录器,即使之前已经配置过。
TrueFalse
False
logging.basicConfig(level=logging.DEBUG, force=True)
errors
指定日志文件编码错误处理方式。
错误处理策略(如 'strict''ignore''replace'
None
logging.basicConfig(filename='app.log', encoding='utf-8', errors='ignore')
import logging # 设置日志基本配置,设置日志级别、输出格式等;详细请看:basicConfig logging.basicConfig( filename='app.log', # 日志输出到文件 filemode='w', # 覆盖写模式 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # 自定义格式 datefmt='%Y-%m-%d %H:%M:%S', # 自定义时间格式 level=logging.DEBUG, # 设置日志级别为 DEBUG encoding='utf-8', # 文件编码 errors='ignore' # 忽略编码错误 ) logging.debug("This is a debug message") logging.info("This is an info message") logging.warning("This is a warning message")

实际示例

import logging # 配置日志 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') def divide(x, y): try: result = x / y except ZeroDivisionError: logging.error("Attempted to divide by zero") else: logging.info(f"The result of {x}/{y} is {result}") if __name__ == "__main__": # 记录日志 logging.debug("Program starts") # 调用函数 divide(10, 2) divide(8, 0) # 记录日志 logging.debug("Program ends")

高级用法

日志组件

  • Logger(日志记录器):负责捕获日志消息并传递给一个或多个 HandlerLogger对象提供了debug()info()warning()error()critical()等方法来记录不同级别的日志。
    • 每个 Logger 都有一个名称,通常与模块名相同(如 __name__)。
    • 支持层级结构,子记录器会继承父记录器的配置。
    • 可以设置日志级别(如 DEBUGINFO 等),只有高于或等于该级别的日志才会被处理。
    • import logging # 创建一个日志记录器 logger = logging.getLogger("my_logger") logger.setLevel(logging.DEBUG)
  • Handler(处理器):负责将日志消息发送到指定的输出位置(如控制台、文件、网络等)。Handler对象可以添加到Logger对象中,以处理相应级别的日志消息。
    • 每个 Handler 可以设置自己的日志级别和输出格式。
    • 一个 Logger 可以绑定多个 Handler,从而实现日志的多路输出。
    • 常见的 Handler 包括:
      • StreamHandler:输出到控制台。
      • FileHandler:输出到文件。
      • RotatingFileHandler:支持日志文件轮换。
      • SMTPHandler:通过邮件发送日志。
      # 创建一个控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # 创建一个文件处理器 file_handler = logging.FileHandler("app.log") file_handler.setLevel(logging.DEBUG)
  • Formatter(格式化器)Formatter对象用于指定日志消息的输出格式。
    • 可以自定义日志消息的显示方式,包括日期、时间、日志级别、消息内容等。
    • 每个 Handler 可以绑定一个 Formatter
    • # 创建一个格式化器 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # 将格式化器绑定到处理器 console_handler.setFormatter(formatter) file_handler.setFormatter(formatter)

完整示例

import logging # 创建日志记录器 logger = logging.getLogger("my_logger") logger.setLevel(logging.DEBUG) # 创建控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # 创建文件处理器 file_handler = logging.FileHandler("app.log") file_handler.setLevel(logging.DEBUG) # 创建格式化器 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # 将格式化器绑定到处理器 console_handler.setFormatter(formatter) file_handler.setFormatter(formatter) # 将处理器添加到记录器 logger.addHandler(console_handler) logger.addHandler(file_handler) # 记录日志 logger.debug("This is a debug message") # 输出到文件 logger.info("This is an info message") # 输出到控制台和文件

日志轮转

使用 RotatingFileHandler 可以限制日志文件的大小,并在文件达到一定大小时创建新的日志文件
import logging import os from logging.handlers import RotatingFileHandler def setup_logger(log_dir='logs', log_file='app.log'): os.makedirs(log_dir, exist_ok=True) # 创建日志目录 log_path = os.path.join(log_dir, log_file) logger = logging.getLogger('my_logger') logger.setLevel(logging.DEBUG) # 文件处理器 - 按大小轮转 size_handler = RotatingFileHandler( log_path, maxBytes=1024*1024, # 1MB backupCount=5, encoding='utf-8' ) # 按时间轮转的handler;没有设置handler级别,会继承logger的DEBUG级别 time_handler = TimedRotatingFileHandler( 'app.log', when='midnight', # 每天午夜 interval=1, # 间隔1天 backupCount=7 # 保留7天 ) # 控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.DEBUG) # 日志格式 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') size_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # 添加处理器 logger.addHandler(size_handler) # 日志输出到文件,按大小轮转 logger.addHandler(console_handler) # 日志输出到控制台 return logger if __name__ == '__main__': logger = setup_logger()

自定义格式(根据日志级别设置颜色)

import logging class CustomFormatter(logging.Formatter): """自定义格式器,为不同级别使用不同颜色""" # 定义不同颜色的ANSI转义码 grey = "\x1b[38;21m" blue = "\x1b[38;5;39m" yellow = "\x1b[38;5;220m" red = "\x1b[38;5;196m" bold_red = "\x1b[31;1m" reset = "\x1b[0m" def __init__(self): super().__init__() # 为不同日志级别定义不同的格式 self.FORMATS = { logging.DEBUG: self.grey + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset, logging.INFO: self.blue + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset, logging.WARNING: self.yellow + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset, logging.ERROR: self.red + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset, logging.CRITICAL: self.bold_red + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset } def format(self, record): # 根据日志级别获取对应的格式 log_fmt = self.FORMATS.get(record.levelno) formatter = logging.Formatter(log_fmt) return formatter.format(record) def custom_formatter_demo(): # 创建logger前先移除所有已存在的handler logger = logging.getLogger(__name__) logger.handlers.clear() logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setFormatter(CustomFormatter()) logger.addHandler(ch) return logger if __name__ == '__main__': logger = custom_formatter_demo() logger.debug('debug message') logger.info('info message') logger.warning('warning message') logger.error('error message') logger.critical('critical message')

其他内容

Logger、Handler、Formatter这三者 和 basicConfig 的关系

  • basicConfig 是一个快捷方式,适合快速配置简单的日志系统。
  • Logger、Handler、Formatter 提供了更灵活和强大的配置能力,适合复杂的场景。
区别
  • basicConfig 只能配置一个全局的日志记录器(root logger),而 Logger 可以创建多个独立的记录器。
  • basicConfig 只能配置一个 Handler,而 Logger 可以绑定多个 Handler
  • basicConfig 的配置是全局的,而 Logger 的配置可以针对不同的模块或功能进行定制。
  • basicConfig:是一个快速配置工具,适合简单场景,但不能替代 LoggerHandler 和 Formatter 的灵活性。

最全示例

# 1. 基础日志记录 import logging # 设置基本配置 logging.basicConfig( level=logging.DEBUG, # 设置日志级别 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # 创建logger对象 logger = logging.getLogger('my_app') # 基本使用示例 def basic_logging_demo(): logger.debug('调试信息') logger.info('一般信息') logger.warning('警告信息') logger.error('错误信息') logger.critical('严重错误信息') # 2. 文件处理器示例 def file_handler_demo(): # 创建文件处理器 file_handler = logging.FileHandler('app.log') file_handler.setLevel(logging.INFO) # 设置格式器 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) # 添加处理器到logger logger.addHandler(file_handler) # 3. 多个处理器示例 def multi_handler_demo(): # 文件处理器 - 记录所有日志 file_handler = logging.FileHandler('full.log') file_handler.setLevel(logging.DEBUG) # 错误文件处理器 - 只记录错误和严重错误 error_handler = logging.FileHandler('error.log') error_handler.setLevel(logging.ERROR) # 控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # 设置格式器 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) error_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # 添加所有处理器 logger.addHandler(file_handler) logger.addHandler(error_handler) logger.addHandler(console_handler) # 4. 配置文件示例 (logging.conf) """ [loggers] keys=root,myApp [handlers] keys=consoleHandler,fileHandler [formatters] keys=simpleFormatter [logger_root] level=DEBUG handlers=consoleHandler [logger_myApp] level=DEBUG handlers=fileHandler qualname=myApp propagate=0 [handler_consoleHandler] class=StreamHandler level=DEBUG formatter=simpleFormatter args=(sys.stdout,) [handler_fileHandler] class=FileHandler level=DEBUG formatter=simpleFormatter args=('app.log', 'w') [formatter_simpleFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s """ # 使用配置文件 def config_file_demo(): import logging.config logging.config.fileConfig('logging.conf') logger = logging.getLogger('myApp') # 5. 日志轮转示例 from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler def rotating_file_demo(): # 按大小轮转 size_handler = RotatingFileHandler( 'app.log', maxBytes=1024*1024, # 1MB backupCount=5 ) # 按时间轮转 time_handler = TimedRotatingFileHandler( 'app.log', when='midnight', # 每天午夜 interval=1, # 间隔1天 backupCount=7 # 保留7天 ) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') size_handler.setFormatter(formatter) time_handler.setFormatter(formatter) logger.addHandler(size_handler) # logger.addHandler(time_handler) # 根据需要选择使用 # 6. 自定义格式器示例 class CustomFormatter(logging.Formatter): """自定义格式器,为不同级别使用不同颜色""" grey = "\x1b[38;21m" blue = "\x1b[38;5;39m" yellow = "\x1b[38;5;226m" red = "\x1b[38;5;196m" bold_red = "\x1b[31;1m" reset = "\x1b[0m" def __init__(self): super().__init__() self.FORMATS = { logging.DEBUG: self.grey + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset, logging.INFO: self.blue + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset, logging.WARNING: self.yellow + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset, logging.ERROR: self.red + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset, logging.CRITICAL: self.bold_red + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + self.reset } def format(self, record): log_fmt = self.FORMATS.get(record.levelno) formatter = logging.Formatter(log_fmt) return formatter.format(record) def custom_formatter_demo(): ch = logging.StreamHandler() ch.setFormatter(CustomFormatter()) logger.addHandler(ch) # 7. 捕获异常示例 def exception_logging_demo(): try: 1/0 except Exception as e: logger.exception("发生除零错误") # 自动记录完整堆栈跟踪 # 8. 上下文管理器示例 class LogContext: def __init__(self, logger, extra): self.logger = logger self.extra = extra def __enter__(self): if self.extra: self.old_extra = self.logger.extra if hasattr(self.logger, 'extra') else {} self.logger.extra = {**self.old_extra, **self.extra} return self.logger def __exit__(self, exc_type, exc_val, exc_tb): if self.extra: self.logger.extra = self.old_extra def context_manager_demo(): with LogContext(logger, {'user': 'admin', 'ip': '192.168.1.1'}): logger.info('用户操作记录') # 将包含额外的上下文信息
对于本文内容有任何疑问, 可与我联系.