日志级别:避免 DEBUG 刷盘
生产环境应避免使用DEBUG级别,否则会记录大量SQL查询和请求细节,导致磁盘迅速写满。建议将根日志器设为INFO,只记录应用错误和关键业务事件。对于Django自带的django.request日志器,可单独设为WARNING,以捕获HTTP 4xx和5xx错误,但排除常见客户端错误(如404)以免日志泛滥。检查方法:在测试环境模拟生产压力,观察日志文件增长速率是否在可接受范围内。
一个常见的做法是先在settings.py中关闭DEBUG,然后配置LOGGING字典。如果你已经用了第三方库(如django-debug-toolbar或silk),记得在生产环境中彻底移除它们的中间件和日志处理器,否则这些库可能绕过你的全局日志级别。验证时,可以尝试触发一个403(比如CSRF校验失败)和一个500错误,确认日志中只出现WARNING及以上级别的记录。如果发现INFO级别消息仍然很多,可以手动将某些第三方库的日志器设为WARNING,例如 logging.getLogger('urllib3').setLevel(logging.WARNING)。
日志轮转:防止单文件撑爆磁盘
必须启用日志轮转,防止单个日志文件无限增长撑爆磁盘。Django本身不直接提供轮转功能,需要通过logging.handlers.RotatingFileHandler或TimedRotatingFileHandler实现。推荐按大小轮转(例如每50MB切分)并保留最近5个备份。注意:如果应用写入频繁,备份数量不宜太少,否则旧日志被覆盖后难以排查历史问题。一个常见坑是忘记设置maxBytes和backupCount,导致轮转失效。
配置时,建议选择RotatingFileHandler并用大小触发,因为时间触发在低流量时段可能产生大量空文件。例如:
'handlers': {
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/django/app.log',
'maxBytes': 52428800, # 50MB
'backupCount': 5,
'formatter': 'verbose',
},
}检查方法:用 dd 或 fallocate 生成一个大文件模拟日志写入,或者直接用生产压力的日志量等待轮转发生。确认日志文件达到 maxBytes 后被切割成 app.log.1、app.log.2 等,并且应用不会因为旧文件被删除而报错。如果运行在Docker容器中,需要确保日志目录挂载了持久卷,否则重启后轮转文件丢失。
日志格式:结构化与可解析
日志格式应包含时间戳、日志级别、模块名、进程ID以及线程ID,便于分布式排查。建议使用ISO 8601格式的时间,并明确添加时区(如UTC)。避免在格式化字符串中拼接过多对象,特别是用户输入,以免引发格式异常或暴露敏感信息。检查方法:部署后随机抽取日志条目,确认每个字段都能正确解析且无额外换行符破坏结构。
一个推荐的格式字符串:'%(asctime)s %(levelname)s %(name)s %(process)d %(thread)d %(message)s'。在formatter中设置 'datefmt': '%Y-%m-%dT%H:%M:%S%z' 即可输出UTC时间。注意,%(message)s 里如果包含换行,某些日志采集系统(如Logstash)可能无法正确处理单条日志。因此建议将消息中的换行替换为空格,或者使用json格式。Django的 django.utils.log.RequireDebugFalse 过滤器可以控制只在生产环境输出特定格式。
敏感信息过滤:避免泄露密码和Token
生产日志中绝对不能明文记录密码、Token、信用卡号等敏感数据。Django的logging过滤器可以在日志记录前修改或丢弃包含敏感字段的日志条目。一个常见做法是编写自定义过滤器,检测关键字(如'password')并替换为'***'。风险边界:过度过滤可能导致关键错误信息丢失,因此只过滤明确敏感的关键字,并定期审查日志确认过滤效果。
实现时,可以继承 logging.Filter 并重写 filter(record) 方法,在方法内修改 record.msg 或添加 record.exc_info 处理。例如:
class SensitiveFilter(logging.Filter):
def filter(self, record):
for kw in ['password', 'token', 'credit_card']:
if kw in record.getMessage().lower():
record.msg = record.msg.replace(kw, '***')
return True然后将此过滤器添加到handlers的filters列表中。检查方法:手动构造一个包含password字段的日志记录,确认输出内容已被脱敏。同时注意如果使用json格式,需要同样处理字典中的键值对。
异步写入与采集
默认的同步日志记录器在每次写入时都会触发磁盘I/O,高并发下可能成为性能瓶颈。可考虑使用QueueHandler将日志消息放入内存队列,再由后台线程异步写入文件。配置时需注意队列大小不能无限增长,否则会导致内存溢出。通常设置maxlen为1000-10000,并监控队列积压情况。如果应用对日志实时性要求极高,异步引入的延迟可能影响故障快速定位,需要权衡。
一个简单的异步配置参考:使用 logging.handlers.QueueHandler 和 QueueListener。在settings.py中创建队列和监听器,确保监听器在应用启动时开始,并在关闭时优雅终止。如果同时使用集中式日志系统(如ELK),建议本地文件作为缓冲,避免网络问题导致日志丢失。检查方法:在高并发场景下观察 /proc/uptime 或应用性能指标,对比同步和异步的磁盘I/O压力。异步配置后,启动时立刻写入一条测试日志,确认监听线程正常工作。