import logging 是 Python 內置的日志記錄模塊,用于替代 print() 進行更專業(yè)的日志管理。
基本用法
- 最簡單的配置
import logging
# 基本配置
logging.basicConfig(level=logging.INFO)
logging.info("這是一條信息")
logging.warning("這是一條警告")
logging.error("這是一條錯誤")
- 不同日志級別
import logging
logging.debug("調試信息") # 級別 10
logging.info("普通信息") # 級別 20
logging.warning("警告信息") # 級別 30
logging.error("錯誤信息") # 級別 40
logging.critical("嚴重錯誤") # 級別 50
常用配置
- 配置輸出格式
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logging.info("程序啟動")
# 輸出: 2024-01-15 10:30:45 - root - INFO - 程序啟動
- 輸出到文件
import logging
logging.basicConfig(
filename='app.log',
filemode='w', # 'w' 覆蓋, 'a' 追加
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logging.info("這條會寫入文件")
- 同時輸出到文件和控制臺
import logging
# 創(chuàng)建 logger
logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)
# 文件 handler
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.INFO)
# 控制臺 handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 設置格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 添加 handler
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# 使用
logger.debug("調試信息")
logger.info("普通信息")
logger.error("錯誤信息")
在函數/類中使用
import logging
def my_function():
logger = logging.getLogger(__name__)
logger.info("函數被調用")
try:
result = 10 / 0
except ZeroDivisionError as e:
logger.error(f"發(fā)生錯誤: {e}", exc_info=True) # exc_info 打印完整堆棧
class MyClass:
def __init__(self):
self.logger = logging.getLogger(self.__class__.__name__)
def do_something(self):
self.logger.info("執(zhí)行操作")
最佳實踐
import logging
# 推薦的通用配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(pathname)s:%(lineno)d - %(message)s',
handlers=[
logging.FileHandler("app.log"),
logging.StreamHandler()
]
)
# 在模塊中
logger = logging.getLogger(__name__)
def main():
logger.info("程序啟動")
# 你的代碼
logger.info("程序結束")
一、logging 模塊架構
logging 模塊采用模塊化設計,包含四個主要組件:
Logger (日志器) → Handler (處理器) → Formatter (格式化器) → 輸出
↓
Filter (過濾器) - 控制日志級別和內容
二、日志級別詳解
import logging
# 預定義級別(數值越高越嚴重)
logging.NOTSET = 0 # 未設置
logging.DEBUG = 10 # 調試信息
logging.INFO = 20 # 常規(guī)信息
logging.WARNING = 30 # 警告信息
logging.ERROR = 40 # 錯誤信息
logging.CRITICAL = 50 # 嚴重錯誤
# 級別判斷示例
logger = logging.getLogger('demo')
logger.setLevel(logging.WARNING)
logger.debug("調試") # 不會輸出(10 < 30)
logger.info("信息") # 不會輸出(20 < 30)
logger.warning("警告") # 會輸出(30 >= 30)
logger.error("錯誤") # 會輸出(40 >= 30)
logger.critical("嚴重") # 會輸出(50 >= 30)
三、Logger 對象詳解
3.1 創(chuàng)建 Logger
import logging
# 創(chuàng)建根日志器(root logger)
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
# 創(chuàng)建命名日志器(推薦)
app_logger = logging.getLogger('myapp')
module_logger = logging.getLogger('myapp.module')
# 層級關系:myapp.module 繼承 myapp 的設置
print(app_logger.parent) # <RootLogger root>
print(module_logger.parent) # <Logger myapp (INFO)>
3.2 Logger 的方法
import logging
logger = logging.getLogger(__name__)
# 基本日志方法
logger.debug('調試信息')
logger.info('普通信息')
logger.warning('警告信息')
logger.error('錯誤信息')
logger.critical('嚴重錯誤')
logger.exception('異常信息') # 等同于 error,但會添加堆棧信息
logger.log(logging.INFO, '自定義級別的消息')
# 檢查級別(避免不必要的計算)
if logger.isEnabledFor(logging.DEBUG):
logger.debug(f'復雜計算: {expensive_function()}')
四、Handler 類型及使用
4.1 常用 Handler
import logging
# 1. StreamHandler - 輸出到控制臺
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 2. FileHandler - 輸出到文件
file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setLevel(logging.INFO)
# 3. RotatingFileHandler - 按大小輪轉文件
from logging.handlers import RotatingFileHandler
rotating_handler = RotatingFileHandler(
'app.log',
maxBytes=1024*1024, # 1MB
backupCount=5 # 保留5個備份
)
# 4. TimedRotatingFileHandler - 按時間輪轉
from logging.handlers import TimedRotatingFileHandler
time_handler = TimedRotatingFileHandler(
'app.log',
when='midnight', # 每天午夜輪轉
interval=1,
backupCount=30 # 保留30天
)
# 5. HTTPHandler - 發(fā)送到遠程服務器
from logging.handlers import HTTPHandler
http_handler = HTTPHandler(
host='localhost:8080',
url='/log',
method='POST'
)
# 6. SMTPHandler - 郵件發(fā)送錯誤
from logging.handlers import SMTPHandler
smtp_handler = SMTPHandler(
mailhost=('smtp.gmail.com', 587),
fromaddr='alert@myapp.com',
toaddrs=['admin@myapp.com'],
subject='應用錯誤',
credentials=('user', 'password')
)
smtp_handler.setLevel(logging.ERROR)
4.2 完整的多 Handler 示例
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
def setup_logger():
"""配置完整的日志系統(tǒng)"""
# 創(chuàng)建主日志器
logger = logging.getLogger('MyApp')
logger.setLevel(logging.DEBUG)
# 1. 控制臺輸出(僅 INFO 及以上)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console_format = logging.Formatter('%(levelname)s - %(message)s')
console.setFormatter(console_format)
# 2. 文件輸出(所有級別)
file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_format = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler.setFormatter(file_format)
# 3. 輪轉文件(錯誤級別單獨記錄)
error_handler = RotatingFileHandler(
'error.log',
maxBytes=10*1024*1024, # 10MB
backupCount=10,
encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_format = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
)
error_handler.setFormatter(error_format)
# 添加所有處理器
logger.addHandler(console)
logger.addHandler(file_handler)
logger.addHandler(error_handler)
return logger
# 使用
logger = setup_logger()
logger.debug('調試信息') # 僅文件
logger.info('普通信息') # 控制臺 + 文件
logger.error('錯誤信息') # 控制臺 + 文件 + 錯誤文件
五、Formatter 格式化詳解
5.1 格式化字段
# 常用格式化字段
format_dict = {
'%(name)s': '日志器名稱',
'%(levelno)s': '日志級別編號',
'%(levelname)s': '日志級別名稱',
'%(pathname)s': '源文件完整路徑',
'%(filename)s': '源文件名',
'%(module)s': '模塊名',
'%(lineno)d': '行號',
'%(funcName)s': '函數名',
'%(created)f': '時間戳',
'%(asctime)s': '可讀時間',
'%(msecs)d': '毫秒部分',
'%(relativeCreated)d': '相對創(chuàng)建時間',
'%(thread)d': '線程ID',
'%(threadName)s': '線程名稱',
'%(process)d': '進程ID',
'%(message)s': '日志消息'
}
# 實際示例
import logging
# 簡單格式
simple_format = logging.Formatter('%(levelname)s: %(message)s')
# 詳細格式
detailed_format = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(filename)s:%(lineno)-4d | %(message)s',
datefmt='%H:%M:%S'
)
# 生產環(huán)境格式
production_format = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(process)d - %(threadName)s - %(message)s'
)
5.2 自定義 Formatter
import logging
import time
class ColoredFormatter(logging.Formatter):
"""彩色控制臺輸出格式器"""
COLORS = {
'DEBUG': '\033[36m', # 青色
'INFO': '\033[32m', # 綠色
'WARNING': '\033[33m', # 黃色
'ERROR': '\033[31m', # 紅色
'CRITICAL': '\033[35m', # 紫色
'RESET': '\033[0m' # 重置
}
def format(self, record):
# 添加顏色
color = self.COLORS.get(record.levelname, self.COLORS['RESET'])
record.levelname = f"{color}{record.levelname}{self.COLORS['RESET']}"
return super().format(record)
# 使用彩色格式器
logger = logging.getLogger()
console = logging.StreamHandler()
formatter = ColoredFormatter('%(levelname)s - %(message)s')
console.setFormatter(formatter)
logger.addHandler(console)
logger.setLevel(logging.DEBUG)
logger.debug('調試信息') # 青色
logger.info('信息') # 綠色
logger.warning('警告') # 黃色
logger.error('錯誤') # 紅色
六、Filter 過濾器
import logging
class CustomFilter(logging.Filter):
"""自定義過濾器:只允許包含特定關鍵字的日志"""
def __init__(self, keyword):
super().__init__()
self.keyword = keyword
def filter(self, record):
# 返回 True 表示允許,F(xiàn)alse 表示拒絕
return self.keyword in record.getMessage()
# 使用過濾器
logger = logging.getLogger('app')
logger.setLevel(logging.DEBUG)
# 添加過濾器
filter_only_db = CustomFilter('database')
logger.addFilter(filter_only_db)
# 測試
logger.info('database connection') # 會輸出
logger.info('user login') # 不會輸出
# 移除過濾器
logger.removeFilter(filter_only_db)
七、配置方式
7.1 代碼配置(最常用)
import logging
# 快速配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler()
]
)
7.2 字典配置(推薦生產環(huán)境)
import logging.config
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
},
'detailed': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(pathname)s:%(lineno)d - %(message)s'
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'INFO',
'formatter': 'standard',
'stream': 'ext://sys.stdout'
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'level': 'DEBUG',
'formatter': 'detailed',
'filename': 'app.log',
'maxBytes': 10485760, # 10MB
'backupCount': 5,
'encoding': 'utf-8'
}
},
'loggers': {
'': { # root logger
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': True
},
'myapp': {
'handlers': ['file'],
'level': 'INFO',
'propagate': False
}
}
}
# 應用配置
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger('myapp')
7.3 文件配置
# logging.conf
[loggers]
keys=root,myapp
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=simpleFormatter,detailedFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_myapp]
level=INFO
handlers=consoleHandler,fileHandler
qualname=myapp
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=logging.handlers.RotatingFileHandler
level=INFO
formatter=detailedFormatter
args=('app.log', 'a', 10485760, 5, 'utf-8')
[formatter_simpleFormatter]
format=%(asctime)s - %(levelname)s - %(message)s
[formatter_detailedFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s
八、完整實戰(zhàn)案例
案例1:Web應用日志系統(tǒng)
import logging
import logging.handlers
import time
from functools import wraps
class WebAppLogger:
"""Web應用日志管理器"""
def __init__(self, app_name, log_dir='./logs'):
self.app_name = app_name
self.log_dir = log_dir
self.logger = self._setup_logger()
def _setup_logger(self):
"""配置日志系統(tǒng)"""
import os
os.makedirs(self.log_dir, exist_ok=True)
logger = logging.getLogger(self.app_name)
logger.setLevel(logging.DEBUG)
# 訪問日志
access_handler = logging.handlers.TimedRotatingFileHandler(
f'{self.log_dir}/access.log',
when='midnight',
interval=1,
backupCount=30
)
access_format = logging.Formatter(
'%(asctime)s - %(remote_ip)s - %(method)s - %(path)s - %(status)d - %(duration)dms'
)
access_handler.setFormatter(access_format)
# 錯誤日志
error_handler = logging.handlers.RotatingFileHandler(
f'{self.log_dir}/error.log',
maxBytes=10*1024*1024,
backupCount=10
)
error_format = logging.Formatter(
'%(asctime)s - %(levelname)s - %(pathname)s:%(lineno)d - %(message)s'
)
error_handler.setFormatter(error_format)
error_handler.setLevel(logging.ERROR)
# 控制臺輸出(開發(fā)環(huán)境)
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
console_format = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console.setFormatter(console_format)
logger.addHandler(access_handler)
logger.addHandler(error_handler)
logger.addHandler(console)
return logger
def log_request(self, func):
"""記錄請求的裝飾器"""
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
# 模擬獲取請求信息
remote_ip = kwargs.get('ip', 'unknown')
method = kwargs.get('method', 'GET')
path = kwargs.get('path', '/')
try:
result = func(*args, **kwargs)
status = 200
return result
except Exception as e:
status = 500
self.logger.error(f"Request failed: {e}", exc_info=True)
raise
finally:
duration = int((time.time() - start_time) * 1000)
# 記錄訪問日志(使用額外參數)
self.logger.info(
'Request processed',
extra={
'remote_ip': remote_ip,
'method': method,
'path': path,
'status': status,
'duration': duration
}
)
return wrapper
# 使用示例
web_logger = WebAppLogger('MyWebApp')
@web_logger.log_request
def handle_request(user_id, **request_info):
"""處理用戶請求"""
web_logger.logger.info(f"Processing user {user_id}")
# 業(yè)務邏輯
return {"user_id": user_id, "status": "success"}
# 模擬請求
handle_request(123, ip='192.168.1.1', method='GET', path='/api/user')
異步日志處理
import logging
import queue
from logging.handlers import QueueHandler, QueueListener
import threading
import time
class AsyncLogger:
"""異步日志處理器"""
def __init__(self):
self.log_queue = queue.Queue(-1)
self.queue_handler = QueueHandler(self.log_queue)
# 配置實際處理器
self.file_handler = logging.FileHandler('async.log')
self.file_handler.setFormatter(
logging.Formatter('%(asctime)s - %(message)s')
)
# 創(chuàng)建監(jiān)聽器
self.listener = QueueListener(
self.log_queue, self.file_handler, respect_handler_level=True
)
# 配置根日志器
root_logger = logging.getLogger()
root_logger.addHandler(self.queue_handler)
root_logger.setLevel(logging.DEBUG)
# 啟動監(jiān)聽器
self.listener.start()
def stop(self):
self.listener.stop()
# 使用異步日志
async_logger = AsyncLogger()
logger = logging.getLogger()
# 日志記錄不會阻塞主線程
for i in range(1000):
logger.info(f"Message {i}")
# 程序結束時停止
async_logger.stop()