You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Gunicorn搭配自定义getMessage()日志时出现日志错误

解决Gunicorn与自定义LogRecord的日志冲突问题

我在Dash应用中自定义了LogRecord类来处理日志中的session_id变量,代码如下:

class LogRecord(logging.LogRecord):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.session_id = None

    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            msg = msg % self.args
            self.session_id = self.args[0]
        return msg


logging.setLogRecordFactory(LogRecord)

直接用Dash启动应用时日志功能正常,但使用Gunicorn启动时,Gunicorn复用了自定义LogRecord却因自身日志参数格式不同触发错误,错误栈如下:

File ".../lib/python3.9/logging/__init__.py", line 1083, in emit
    msg = self.format(record)
  File ".../lib/python3.9/logging/__init__.py", line 927, in format
    return fmt.format(record)
  File ".../lib/python3.9/logging/__init__.py", line 663, in format
    record.message = record.getMessage()
  File ".../LipViz/log/classes_log.py", line 17, in getMessage
    self.session_id = self.args[0]
  File ".../lib/python3.9/site-packages/gunicorn/glogging.py", line 108, in __getitem__
    if k.startswith("{"):
AttributeError: 'int' object has no attribute 'startswith'

问题原因

Gunicorn自身日志使用了自定义参数数组,与应用日志的参数格式不兼容,复用全局替换的LogRecord后,自定义getMessage()逻辑处理Gunicorn日志时触发类型错误。

解决方案

核心是区分日志来源,让Gunicorn日志使用原生getMessage(),应用日志保留自定义逻辑,以下是两种可行实现:

方式一:基于日志名称前缀做分支处理

Gunicorn的日志器名称默认以gunicorn.开头,在自定义getMessage()中判断日志名称,针对性执行逻辑:

import logging

class LogRecord(logging.LogRecord):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.session_id = None

    def getMessage(self):
        # Gunicorn日志直接调用原生实现
        if self.name.startswith("gunicorn."):
            return super().getMessage()
        
        # 应用日志的自定义逻辑,同时增加类型校验避免意外报错
        msg = str(self.msg)
        if self.args:
            msg = msg % self.args
            if self.args and isinstance(self.args[0], str):
                self.session_id = self.args[0]
        return msg

logging.setLogRecordFactory(LogRecord)

方式二:为应用日志器单独添加过滤器

不全局替换LogRecordFactory,而是给应用专属日志器绑定过滤器,仅在应用日志中注入session_id:

import logging

class SessionIDFilter(logging.Filter):
    def filter(self, record):
        # 仅处理应用日志(需提前定义应用日志器名称,比如"my_dash_app")
        if not record.name.startswith("my_dash_app"):
            return True
        
        # 从参数中提取session_id,增加容错判断
        if hasattr(record, 'args') and record.args and isinstance(record.args[0], str):
            record.session_id = record.args[0]
        else:
            record.session_id = None
        return True

# 配置应用专属日志器
app_logger = logging.getLogger("my_dash_app")
app_logger.addFilter(SessionIDFilter())

# 应用中使用该日志器记录日志
# app_logger.info("User performed action: %s", session_id)

方案对比

  • 方式一:改动量小,适配已有全局日志的代码结构,只需在原自定义类中增加分支判断。
  • 方式二:解耦性更强,不会干扰其他日志系统,适合新应用或日志架构清晰的场景。

内容的提问来源于stack exchange,提问作者Luggie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 12:01:50