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 ,则会强制重新配置日志记录器,即使之前已经配置过。 | True 、False | 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(日志记录器):负责捕获日志消息并传递给一个或多个
Handler
。Logger
对象提供了debug()
、info()
、warning()
、error()
和critical()
等方法来记录不同级别的日志。 - 每个
Logger
都有一个名称,通常与模块名相同(如__name__
)。 - 支持层级结构,子记录器会继承父记录器的配置。
- 可以设置日志级别(如
DEBUG
、INFO
等),只有高于或等于该级别的日志才会被处理。
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
:是一个快速配置工具,适合简单场景,但不能替代Logger
、Handler
和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('用户操作记录') # 将包含额外的上下文信息