1. Python日志记录(Logging)最佳实践
日志记录是任何应用程序不可或缺的一部分,它就像应用程序的"黑匣子",记录了系统运行时的关键信息。作为一名有多年Python开发经验的工程师,我见过太多因为日志记录不当而导致的调试噩梦。今天,我将分享Python标准库logging模块的最佳实践,这些经验来自于我参与过的多个大型项目,包括处理日均千万级请求的Web服务。
日志记录不仅仅是简单的print语句替代品,它是一个完整的系统,需要考虑日志级别、格式、输出目标、性能影响等多个方面。合理的日志记录可以帮助你快速定位生产环境中的问题,而不当的日志配置则可能导致磁盘爆满或关键信息丢失。
2. 日志系统核心概念解析
2.1 日志级别及其应用场景
Python的logging模块定义了6个日志级别,每个级别都有特定的使用场景:
DEBUG(10):最详细的日志信息,通常只在开发阶段使用。比如记录函数的输入参数、中间计算结果等。
INFO(20):确认程序按预期运行的信息性消息。比如"用户登录成功"、"订单已创建"等业务流程关键点。
WARNING(30):表明可能有问题发生,但程序还能继续运行。比如"磁盘空间不足80%"、"使用默认配置"等。
ERROR(40):由于更严重的问题,软件已不能执行某些功能。比如"数据库连接失败"、"支付接口调用异常"等。
CRITICAL(50):严重的错误,可能导致程序无法继续运行。比如"内存耗尽"、"关键配置文件丢失"等。
NOTSET(0):所有消息都会被记录,通常只在根logger上设置。
提示:在生产环境中,通常将日志级别设置为INFO或WARNING,在开发环境可以设置为DEBUG。避免在生产环境使用DEBUG级别,因为它会产生大量日志并影响性能。
2.2 Logger、Handler、Filter和Formatter
Python的日志系统由四个主要组件组成:
Logger:应用程序直接使用的接口,负责产生日志记录。可以设置日志级别和传播规则。
Handler:决定日志记录的输出位置,如控制台、文件、网络等。每个handler可以有自己的日志级别和格式。
Filter:提供了更细粒度的控制,决定哪些日志记录会被输出。
Formatter:指定日志记录的最终输出格式。
这种组件化的设计使得日志系统非常灵活,你可以为不同的模块设置不同的日志级别,或者将错误日志发送到邮件而将调试日志写入文件。
3. 基础配置与使用
3.1 快速开始:基础日志配置
最简单的日志配置方式是使用basicConfig()方法,这适合小型应用或脚本:
import logging # 基本配置 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', filename='app.log', filemode='a' ) # 使用日志 logging.info('程序启动') try: 1 / 0 except ZeroDivisionError: logging.error('除零错误发生', exc_info=True)这段代码配置了日志级别为INFO,格式包含时间、logger名称、级别和消息,输出到app.log文件。exc_info=True会在日志中记录完整的异常堆栈。
3.2 高级配置:字典配置与文件配置
对于复杂的应用程序,推荐使用字典或配置文件来配置日志系统。以下是使用字典配置的例子:
import logging.config LOGGING_CONFIG = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'level': 'DEBUG', 'formatter': 'standard', 'stream': 'ext://sys.stdout' }, 'file': { 'class': 'logging.handlers.RotatingFileHandler', 'level': 'INFO', 'formatter': 'standard', 'filename': 'app.log', 'maxBytes': 10485760, # 10MB 'backupCount': 5, 'encoding': 'utf8' }, }, 'loggers': { '': { # root logger 'handlers': ['console', 'file'], 'level': 'DEBUG', 'propagate': False }, 'my_module': { 'handlers': ['file'], 'level': 'INFO', 'propagate': False } } } logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger(__name__)这种配置方式提供了更精细的控制,包括:
- 不同的formatter用于不同的handler
- RotatingFileHandler实现日志轮转,防止单个文件过大
- 为不同模块设置不同的日志级别和handler
- 控制日志传播行为
4. 生产环境最佳实践
4.1 结构化日志记录
在现代应用程序中,特别是使用ELK(Elasticsearch, Logstash, Kibana)等日志分析系统时,结构化日志变得非常重要。Python的logging模块可以通过自定义Formatter实现JSON格式的日志:
import json import logging from datetime import datetime class JsonFormatter(logging.Formatter): def format(self, record): log_record = { 'timestamp': datetime.utcnow().isoformat(), 'level': record.levelname, 'message': record.getMessage(), 'logger': record.name, 'module': record.module, 'function': record.funcName, 'line': record.lineno, } if record.exc_info: log_record['exception'] = self.formatException(record.exc_info) return json.dumps(log_record) # 配置使用JsonFormatter handler = logging.StreamHandler() handler.setFormatter(JsonFormatter()) logger = logging.getLogger() logger.addHandler(handler) logger.setLevel(logging.INFO) # 使用示例 logger.info('用户登录成功', extra={'user_id': 12345, 'ip': '192.168.1.1'})结构化日志使得日志分析工具能够更容易地解析和索引日志内容,实现更强大的查询和可视化功能。
4.2 日志轮转与归档策略
在生产环境中,必须考虑日志文件的轮转和归档,以防止磁盘空间被占满。Python提供了几种Handler来实现这一功能:
- RotatingFileHandler:基于文件大小的轮转
from logging.handlers import RotatingFileHandler handler = RotatingFileHandler( 'app.log', maxBytes=10*1024*1024, # 10MB backupCount=5, encoding='utf-8' )- TimedRotatingFileHandler:基于时间的轮转
from logging.handlers import TimedRotatingFileHandler handler = TimedRotatingFileHandler( 'app.log', when='midnight', interval=1, backupCount=7, encoding='utf-8' )注意:在Windows系统上,由于文件锁定机制,可能需要先关闭现有的handler才能成功轮转文件。
4.3 性能优化技巧
日志记录虽然重要,但不合理的实现可能成为性能瓶颈。以下是一些优化建议:
- 避免在热路径上进行字符串格式化:
# 不推荐 - 即使日志级别高于DEBUG也会执行字符串格式化 logger.debug('User %s logged in from %s', user, ip) # 推荐 - 先检查日志级别 if logger.isEnabledFor(logging.DEBUG): logger.debug('User %s logged in from %s', user, ip)使用适当的日志级别:生产环境避免使用DEBUG级别,它会生成大量日志并影响性能。
异步日志记录:对于高吞吐量应用,考虑使用异步handler,如
concurrent-log-handler包提供的ConcurrentRotatingFileHandler。合理设置传播:避免不必要的日志传播,特别是当使用多个handler时。
5. 高级应用场景
5.1 多模块应用的日志管理
在大型应用中,通常需要为不同模块设置不同的日志级别。Python的logging模块支持层次化的logger命名:
# 主模块 import logging logging.basicConfig(level=logging.WARNING) logger = logging.getLogger(__name__) # 子模块 module_logger = logging.getLogger('main.submodule') module_logger.setLevel(logging.DEBUG) # 只为这个子模块设置DEBUG级别logger名称使用点号分隔形成层次结构,例如:
- 'app' (主应用)
- 'app.models' (模型层)
- 'app.views' (视图层)
父logger的设置可以被子logger继承,除非子logger明确设置了不同的值。
5.2 上下文感知的日志记录
在Web应用或异步任务中,通常需要在日志中包含请求ID、用户ID等上下文信息。有几种实现方式:
- 使用Filter添加上下文:
class ContextFilter(logging.Filter): def filter(self, record): record.request_id = get_current_request_id() # 从线程局部存储等获取 return True logger.addFilter(ContextFilter()) formatter = logging.Formatter('%(asctime)s [%(request_id)s] %(message)s')- 使用LoggerAdapter:
logger = logging.getLogger(__name__) adapter = logging.LoggerAdapter(logger, {'request_id': '12345'}) adapter.info('Processing request')- 使用structlog等第三方库:
import structlog structlog.configure( processors=[ structlog.processors.JSONRenderer() ] ) log = structlog.get_logger() log.info('user_login', user_id=123, ip='192.168.1.1')5.3 分布式系统中的日志收集
在微服务架构中,日志分散在各个服务中,需要集中收集和分析。常见的解决方案包括:
- ELK Stack(Elasticsearch + Logstash + Kibana)
- Fluentd
- Splunk
- AWS CloudWatch Logs
Python应用可以通过配置适当的handler将日志发送到这些系统。例如,使用python-logstash将日志发送到Logstash:
from logstash_async.handler import AsynchronousLogstashHandler logstash_handler = AsynchronousLogstashHandler( host='localhost', port=5959, database_path='logstash.db' ) logger.addHandler(logstash_handler)6. 常见问题与解决方案
6.1 日志不显示或输出不正确
问题现象:配置了日志但看不到输出,或者格式不符合预期。
排查步骤:
- 检查logger的有效级别:
logger.getEffectiveLevel() - 确认handler是否添加到logger:
logger.handlers - 检查handler的级别是否高于logger的级别
- 确认日志没有因为传播而被父logger过滤
典型解决方案:
# 确保handler已添加 if not logger.handlers: logger.addHandler(logging.StreamHandler()) # 确保日志级别设置正确 logger.setLevel(logging.DEBUG) for handler in logger.handlers: handler.setLevel(logging.DEBUG)6.2 日志文件权限问题
问题现象:应用无法写入日志文件,特别是在使用不同用户运行的应用中。
解决方案:
- 确保运行用户对日志目录有写权限
- 使用
os.umask设置适当的文件创建权限 - 考虑使用
logging.handlers.WatchedFileHandler,它会在文件被移动或删除后自动重新打开
import os os.umask(0o002) # 设置文件创建时的权限掩码 from logging.handlers import WatchedFileHandler handler = WatchedFileHandler('app.log')6.3 日志性能问题
问题现象:应用性能下降,特别是在高负载时。
优化建议:
- 使用异步日志handler
- 减少不必要的日志记录,特别是DEBUG级别
- 避免在日志消息中进行复杂的计算或IO操作
- 考虑使用更快的JSON序列化库,如
orjson代替标准库的json
# 使用orjson加速JSON日志格式化 import orjson class FastJsonFormatter(logging.Formatter): def format(self, record): log_record = {...} # 同上 return orjson.dumps(log_record).decode('utf-8')7. 日志安全注意事项
日志中可能包含敏感信息,如密码、API密钥、个人信息等,必须小心处理:
- 避免记录敏感数据:
# 不推荐 logger.info(f'用户登录,密码: {password}') # 推荐 logger.info('用户登录', extra={'user': username})- 实现敏感数据过滤:
class SensitiveDataFilter(logging.Filter): def filter(self, record): if hasattr(record, 'password'): record.password = '***REDACTED***' return True logger.addFilter(SensitiveDataFilter())- 控制日志访问权限:
- 确保日志文件只能被授权用户访问
- 在生产环境中,避免将日志写入web可访问的目录
- 考虑使用加密的日志存储
- 合规性考虑:
- 根据GDPR等法规要求,可能需要定期清理包含个人数据的日志
- 确保日志保留策略符合组织政策和法律法规
8. 实战:完整的日志配置示例
下面是一个综合了上述最佳实践的完整日志配置示例,适合大多数Python Web应用:
import logging import logging.config from logging.handlers import RotatingFileHandler import os import json from datetime import datetime def setup_logging(): LOG_DIR = 'logs' os.makedirs(LOG_DIR, exist_ok=True) class JsonFormatter(logging.Formatter): def format(self, record): log_record = { 'timestamp': datetime.utcnow().isoformat(), 'level': record.levelname, 'logger': record.name, 'message': record.getMessage(), 'module': record.module, 'function': record.funcName, 'line': record.lineno, } if record.exc_info: log_record['exception'] = self.formatException(record.exc_info) return json.dumps(log_record, ensure_ascii=False) # 基础配置 logging_config = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'json': { '()': JsonFormatter, }, 'simple': { 'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'level': 'INFO', 'formatter': 'simple', 'stream': 'ext://sys.stdout' }, 'file': { '()': RotatingFileHandler, 'level': 'INFO', 'formatter': 'json', 'filename': os.path.join(LOG_DIR, 'app.log'), 'maxBytes': 10 * 1024 * 1024, # 10MB 'backupCount': 5, 'encoding': 'utf8' }, 'error_file': { '()': RotatingFileHandler, 'level': 'ERROR', 'formatter': 'json', 'filename': os.path.join(LOG_DIR, 'error.log'), 'maxBytes': 10 * 1024 * 1024, 'backupCount': 5, 'encoding': 'utf8' }, }, 'loggers': { '': { # root logger 'handlers': ['console', 'file', 'error_file'], 'level': 'INFO', }, 'app.db': { 'handlers': ['file'], 'level': 'DEBUG', 'propagate': False, }, 'app.api': { 'handlers': ['console', 'file'], 'level': 'INFO', 'propagate': False, }, }, } logging.config.dictConfig(logging_config) # 添加全局过滤器 class SensitiveDataFilter(logging.Filter): def filter(self, record): if hasattr(record, 'password'): record.password = '***REDACTED***' return True for handler in logging.root.handlers: handler.addFilter(SensitiveDataFilter()) # 初始化日志配置 setup_logging() # 获取logger实例 logger = logging.getLogger(__name__) db_logger = logging.getLogger('app.db') api_logger = logging.getLogger('app.api')这个配置实现了:
- 多级别日志记录(INFO到文件,ERROR单独文件)
- JSON格式的结构化日志
- 日志轮转防止磁盘空间耗尽
- 敏感数据过滤
- 不同模块的不同日志级别配置
- 控制台和文件双重输出
9. 日志监控与告警
仅仅记录日志是不够的,还需要监控日志并设置适当的告警。常见的做法包括:
- 关键错误告警:监控ERROR及以上级别的日志,通过邮件、Slack等通知开发团队
class AlertHandler(logging.Handler): def emit(self, record): if record.levelno >= logging.ERROR: send_alert(record.getMessage()) # 实现发送告警的逻辑 logger.addHandler(AlertHandler())异常模式检测:使用ELK或Splunk等工具检测异常日志模式,如突然增加的ERROR日志
心跳监控:确保应用定期产生心跳日志,没有心跳则触发告警
性能指标提取:从日志中提取请求处理时间等指标,监控性能变化
日志采样:在高负载情况下,可以对DEBUG日志进行采样而不是记录全部,以平衡详细度和性能
10. 日志与应用程序健康检查
日志可以作为应用程序健康检查的重要组成部分。一些实践建议:
- 启动日志:应用启动时记录关键配置和环境信息
logger.info('应用启动', extra={ 'config': sanitized_config, 'environment': os.environ.get('ENV', 'development') })- 健康检查端点:在Web应用中添加
/health端点,检查依赖服务状态并记录
@app.route('/health') def health_check(): try: db_status = check_database() cache_status = check_cache() logger.info('健康检查', extra={ 'db_status': db_status, 'cache_status': cache_status }) return jsonify({'status': 'healthy'}), 200 except Exception as e: logger.error('健康检查失败', exc_info=True) return jsonify({'status': 'unhealthy'}), 500- 定期状态日志:记录内存使用、线程数等系统指标
import psutil import threading def log_system_stats(): logger.info('系统状态', extra={ 'memory': psutil.virtual_memory().percent, 'cpu': psutil.cpu_percent(), 'threads': threading.active_count() }) threading.Timer(300, log_system_stats).start() # 每5分钟记录一次 # 启动状态监控 log_system_stats()- 优雅关闭:捕获终止信号并记录关闭原因
import signal def handle_shutdown(signum, frame): logger.info('接收到关闭信号 %s', signum) # 执行清理操作 sys.exit(0) signal.signal(signal.SIGTERM, handle_shutdown) signal.signal(signal.SIGINT, handle_shutdown)11. 日志测试与验证
确保日志系统正常工作同样需要测试。一些测试策略:
- 单元测试日志输出:
import unittest from io import StringIO class TestLogging(unittest.TestCase): def setUp(self): self.stream = StringIO() handler = logging.StreamHandler(self.stream) logger = logging.getLogger('test') logger.setLevel(logging.INFO) logger.handlers = [handler] self.logger = logger def test_log_output(self): self.logger.info('测试消息') self.assertIn('测试消息', self.stream.getvalue())- 集成测试日志配置:验证生产配置是否正确加载
def test_log_config(): setup_logging() logger = logging.getLogger(__name__) assert logger.level == logging.INFO assert any(isinstance(h, RotatingFileHandler) for h in logger.handlers)- 日志内容断言:在功能测试中验证特定日志是否产生
def test_login_logging(): with self.assertLogs('app.auth', level='INFO') as cm: login_user('test', 'password') self.assertIn('用户test登录成功', cm.output[0])- 性能测试:评估日志系统对应用性能的影响
import timeit def test_log_performance(): def log_messages(): for i in range(1000): logger.info('测试消息 %d', i) time = timeit.timeit(log_messages, number=10) assert time < 1.0 # 10000条日志应在1秒内完成12. 日志与审计跟踪
在某些领域(如金融、医疗),日志不仅用于调试,还用于合规审计。这时需要考虑:
不可篡改性:确保日志一旦写入就不能被修改,可以考虑:
- 将日志写入只读介质
- 使用区块链技术存储日志哈希
- 定期将日志归档到安全存储
完整时间戳:使用高精度、同步的时间源
from datetime import datetime, timezone logger.info('关键操作', extra={ 'timestamp': datetime.now(timezone.utc).isoformat(), 'user': current_user.id, 'action': 'delete_record' })- 操作关联:使用唯一ID关联相关操作
import uuid request_id = str(uuid.uuid4()) def process_request(request): logger.info('开始处理请求', extra={'request_id': request_id}) # ...处理逻辑... logger.info('请求处理完成', extra={'request_id': request_id})长期保留:根据法规要求保留日志足够长时间
- 实现自动归档和过期策略
- 考虑冷热存储分层,近期日志在快速存储,旧日志在廉价存储
访问控制:限制谁可以访问审计日志
- 日志文件设置严格的文件权限
- 查询接口实现认证和授权
- 记录所有对审计日志的访问
13. 日志与错误追踪系统集成
现代错误追踪系统(如Sentry、Rollbar)可以与日志系统集成,提供更强大的错误监控能力:
- Sentry集成示例:
import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration # 所有日志级别>=ERROR的事件都会发送到Sentry sentry_logging = LoggingIntegration( level=logging.INFO, # 捕获INFO及以上作为breadcrumbs event_level=logging.ERROR # 发送ERROR及以上作为事件 ) sentry_sdk.init( dsn="your-dsn-here", integrations=[sentry_logging], traces_sample_rate=1.0 ) # 普通日志使用方式不变 logger.error('发生了严重错误', extra={'data': {'user_id': 123}})- 错误上下文增强:
try: risky_operation() except Exception as e: logger.error('操作失败', exc_info=True, extra={ 'context': { 'user': current_user.id, 'data': sanitized_data } }) raise- 性能监控集成:
# 在Sentry中跟踪事务 with sentry_sdk.start_transaction(op="task", name="process_data"): logger.info('开始处理数据') # ...处理逻辑... logger.info('数据处理完成')- 自定义指纹:控制错误分组方式
try: process_payment() except PaymentError as e: logger.error('支付处理失败', exc_info=True, extra={ 'fingerprint': ['payment', str(e.code)] })14. 日志与指标监控系统集成
日志可以与Prometheus、StatsD等指标监控系统结合,提供更全面的可观测性:
- 从日志提取指标:
from prometheus_client import Counter LOG_ERRORS = Counter('app_log_errors', 'Number of ERROR logs', ['logger']) class MetricsFilter(logging.Filter): def filter(self, record): if record.levelno >= logging.ERROR: LOG_ERRORS.labels(logger=record.name).inc() return True logger.addFilter(MetricsFilter())- 记录性能指标:
import time from prometheus_client import Histogram REQUEST_TIME = Histogram('request_processing_seconds', 'Time spent processing request') def process_request(request): start_time = time.time() logger.info('开始处理请求') try: # ...处理逻辑... logger.info('请求处理成功') finally: duration = time.time() - start_time REQUEST_TIME.observe(duration) logger.info('请求处理完成', extra={'duration': duration})- 结构化日志与指标关联:
# 日志 logger.info('API响应', extra={ 'endpoint': '/users', 'status_code': 200, 'response_time': 0.45 }) # 对应的Prometheus指标 API_RESPONSES = Counter('api_responses', 'API responses', ['endpoint', 'status_code']) API_DURATION = Histogram('api_duration', 'API response time', ['endpoint']) class ApiMetricsFilter(logging.Filter): def filter(self, record): if hasattr(record, 'endpoint'): API_RESPONSES.labels( endpoint=record.endpoint, status_code=getattr(record, 'status_code', 0) ).inc() if hasattr(record, 'response_time'): API_DURATION.labels( endpoint=record.endpoint ).observe(record.response_time) return True15. 日志与分布式追踪系统集成
在微服务架构中,分布式追踪系统(如Jaeger、Zipkin)可以帮助跟踪请求在多个服务间的流转:
- 传播追踪上下文:
from opentelemetry import trace from opentelemetry.propagate import inject, extract # 在发送请求前注入追踪上下文 headers = {} inject(headers) logger.info('发送请求', extra={'headers': headers}) # 在接收请求时提取追踪上下文 context = extract(headers) tracer = trace.get_tracer(__name__) with tracer.start_as_current_span('process', context=context): logger.info('处理请求')- 在日志中包含追踪ID:
class TracingFilter(logging.Filter): def filter(self, record): span = trace.get_current_span() if span.is_recording(): record.trace_id = format_trace_id(span.get_span_context().trace_id) record.span_id = format_span_id(span.get_span_context().span_id) return True logger.addFilter(TracingFilter()) formatter = logging.Formatter('%(trace_id)s %(span_id)s %(message)s')- 关联日志与追踪:
with tracer.start_as_current_span('database.query'): logger.info('执行数据库查询', extra={ 'query': sanitized_query, 'parameters': parameters }) # ...执行查询...- 采样决策传播:
def should_log(record): span = trace.get_current_span() if span.is_recording(): # 如果span被采样,则记录详细日志 return True # 否则只记录错误 return record.levelno >= logging.ERROR class SamplingFilter(logging.Filter): def filter(self, record): return should_log(record)16. 日志与机器学习
日志数据可以用于训练机器学习模型,用于异常检测、预测性维护等场景:
- 日志模式挖掘:
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.cluster import KMeans # 从日志中提取文本特征 logs = [log.message for log in query_logs()] vectorizer = TfidfVectorizer(max_features=1000) X = vectorizer.fit_transform(logs) # 聚类分析日志模式 kmeans = KMeans(n_clusters=10) clusters = kmeans.fit_predict(X) # 分析每个簇的特征 for i in range(10): cluster_logs = [log for log, c in zip(logs, clusters) if c == i] logger.info(f'日志簇{i}示例', extra={'examples': cluster_logs[:3]})- 异常检测:
from sklearn.ensemble import IsolationForest # 训练异常检测模型 model = IsolationForest(contamination=0.01) model.fit(X) # 预测新日志是否异常 new_logs = [log.message for log in query_recent_logs()] X_new = vectorizer.transform(new_logs) anomalies = model.predict(X_new) == -1 for log, is_anomaly in zip(new_logs, anomalies): if is_anomaly: logger.warning('异常日志检测', extra={'log': log})- 预测性维护:
import pandas as pd from prophet import Prophet # 将错误日志按时间聚合 error_logs = query_error_logs() df = pd.DataFrame({ 'ds': [log.timestamp for log in error_logs], 'y': 1 }).groupby('ds').sum().reset_index() # 预测未来错误趋势 model = Prophet() model.fit(df) future = model.make_future_dataframe(periods=30) forecast = model.predict(future) # 如果预测错误率上升,触发告警 if forecast.tail(30)['yhat'].mean() > df['y'].mean() * 1.5: logger.error('预测错误率将显著上升')- 日志分类:
from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression # 准备已标记的训练数据 X_train = [log.message for log in training_logs] y_train = [log.label for log in training_logs] # 训练分类器 text_clf = Pipeline([ ('tfidf', TfidfVectorizer()), ('clf', LogisticRegression()) ]) text_clf.fit(X_train, y_train) # 分类新日志 new_logs = [log.message for log in unlabeled_logs] predictions = text_clf.predict(new_logs) for log, label in zip(new_logs, predictions): logger.info('自动分类日志', extra={ 'log': log, 'predicted_label': label })17. 日志与安全分析
日志是安全分析的重要数据源,可以用于检测入侵、异常行为等:
- 暴力破解检测:
from collections import defaultdict failed_logins = defaultdict(int) class SecurityFilter(logging.Filter): def filter(self, record): if (record.getMessage().startswith('登录失败') and hasattr(record, 'ip')): failed_logins[record.ip] += 1 if failed_logins[record.ip] > 5: logger.warning('可能的暴力破解尝试', extra={ 'ip': record.ip, 'count': failed_logins[record.ip] }) return True logger.addFilter(SecurityFilter())- 敏感操作监控:
SENSITIVE_ACTIONS = ['delete', 'grant', 'revoke', 'admin'] class SensitiveActionFilter(logging.Filter): def filter(self, record): if hasattr(record, 'action'): if any(action in record.action for action in SENSITIVE_ACTIONS): logger.info('敏感操作记录', extra={ 'user': getattr(record, 'user', 'unknown'), 'action': record.action, 'target': getattr(record, 'target', 'unknown') }) return True- 异常时间活动检测:
import datetime class UnusualTimeFilter(logging.Filter): def filter(self, record): if hasattr(record, 'timestamp'): hour = datetime.datetime.fromisoformat(record.timestamp).hour if hour < 8 or hour > 20: # 非工作时间 logger.warning('非工作时间活动', extra={ 'user': getattr(record, 'user', 'unknown'), 'time': record.timestamp, 'action': getattr(record, 'action', 'unknown') }) return True- 数据泄露检测:
import re PATTERNS = [ r'\b\d{4}[ -]?\d{4}[ -]?\d{4}[ -]?\d{4}\b', # 信用卡 r'\b\d{3}[ -]?\d{2}[ -]?\d{4}\b', # SSN # 其他敏感数据模式 ] class DataLeakFilter(logging.Filter): def filter(self, record): message = record.getMessage() for pattern in PATTERNS: if re.search(pattern, message): logger.error('可能的敏感数据泄露', extra={ 'pattern': pattern, 'log': message }) # 可以进一步redact敏感数据 record.msg = re.sub(pattern, '***REDACTED***', message) return True18. 日志与合规性
在许多行业,日志记录是合规性要求的一部分。需要考虑:
- 数据保留策略:
from datetime import datetime, timedelta def cleanup_old_logs(): cutoff = datetime.now() - timedelta(days=365) # 保留1年 old_logs = query_logs(before=cutoff) for log in old_logs: if not is_required_for_compliance(log): delete_log(log)- 访问审计:
class AccessAuditFilter(logging.Filter): def filter(self, record): if hasattr(record, 'access_type'): logger.info('访问审计', extra={ 'user': record.user, 'resource': record.resource, 'action': record.action, 'timestamp': datetime.now(timezone.utc).isoformat() }) return True- 不可否认性:
import hashlib def create_log_hash(record): log_data = json.dumps({ 'timestamp': record.timestamp, 'message': record.message, 'user': record.user }, sort_keys=True).encode('utf-8') return hashlib.sha256(log_data).hexdigest() class NonRepudiationFilter(logging.Filter): def filter(self, record): record.hash = create_log_hash(record) return True- 合规性报告:
def generate_compliance_report(): report = { 'period': {