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

Gunicorn日志重复问题及自定义Stackdriver JSON日志模块咨询

Fixing Gunicorn Duplicate Logs + Stackdriver JSON Formatter Setup

First, let's break this into two clear parts: resolving Gunicorn's duplicate logging issue, and refining your custom formatter to properly fit Stackdriver's JSON requirements.

Step 1: Why Logs Duplicate

Gunicorn sets up its own default log handlers, and most web frameworks (like Flask/Django) do the same. Duplicates happen when:

  • Your app's logs propagate up to Gunicorn's root logger, triggering both handlers to process the same message
  • Accidentally adding multiple handlers to the same logger instance

Step 2: Complete Your Stackdriver JSON Formatter

Your existing code is a solid start—let's finish it to match Stackdriver's required fields (like severity and timestamp). Save this as stackdriver_logging.py:

import logging
import sys
from pythonjsonlogger import jsonlogger
from datetime import datetime
import iso8601

class _MaxLevelFilter(object):
    def __init__(self, highest_log_level):
        self._highest_log_level = highest_log_level
    def filter(self, log_record):
        return log_record.levelno <= self._highest_log_level

class StackdriverJsonFormatter(jsonlogger.JsonFormatter):
    def __init__(self, **kwargs):
        # Base fields to include in every log
        super().__init__(
            fmt="%(message)s %(levelname)s %(asctime)s %(module)s %(lineno)d",
            **kwargs
        )

    def formatRecord(self, record):
        # Override to shape logs for Stackdriver compatibility
        log_dict = super().formatRecord(record)
        
        # Map to Stackdriver's required and recommended fields
        stackdriver_log = {
            "message": log_dict["message"],
            "severity": log_dict["levelname"],
            "timestamp": iso8601.to_string(datetime.fromtimestamp(record.created)),
            "module": log_dict["module"],
            "line_number": log_dict["lineno"]
        }
        
        # Add exception details if the log includes an error trace
        if record.exc_info:
            stackdriver_log["exception"] = self.formatException(record.exc_info)
        
        # Include any custom extra fields attached to the log record
        if hasattr(record, "extra"):
            stackdriver_log.update(record.extra)
        
        return stackdriver_log

Step 3: Configure Gunicorn to Stop Duplicates & Use the Formatter

Create a Gunicorn config file (e.g., gunicorn.conf.py) to take full control of logging:

import logging
import sys
from stackdriver_logging import StackdriverJsonFormatter, _MaxLevelFilter

def setup_logging():
    # Grab Gunicorn's root logger instance
    gunicorn_logger = logging.getLogger("gunicorn")
    
    # Remove all default handlers to eliminate duplicate processing
    for handler in gunicorn_logger.handlers[:]:
        gunicorn_logger.removeHandler(handler)
    
    # Split logs to follow standard practice: INFO+ below to stdout, WARNING+ to stderr
    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.addFilter(_MaxLevelFilter(logging.INFO))
    
    stderr_handler = logging.StreamHandler(sys.stderr)
    stderr_handler.setLevel(logging.WARNING)
    
    # Attach our Stackdriver formatter to both handlers
    formatter = StackdriverJsonFormatter()
    stdout_handler.setFormatter(formatter)
    stderr_handler.setFormatter(formatter)
    
    # Add the configured handlers back to Gunicorn's logger
    gunicorn_logger.addHandler(stdout_handler)
    gunicorn_logger.addHandler(stderr_handler)
    
    # Disable log propagation to prevent logs from bubbling up to the root logger
    gunicorn_logger.propagate = False

# Run the logging setup when Gunicorn starts
setup_logging()

# Your standard Gunicorn server configs
bind = "0.0.0.0:8000"
workers = 2
timeout = 30

Step 4: Configure Your App to Avoid Duplicates

Make sure your web app doesn't add extra handlers or propagate logs to Gunicorn.

For Flask Apps:

from flask import Flask
import logging
from stackdriver_logging import StackdriverJsonFormatter

app = Flask(__name__)

# Stop app logs from propagating to Gunicorn's logger
app.logger.propagate = False

# Remove Flask's default handlers to avoid conflicts
for handler in app.logger.handlers[:]:
    app.logger.removeHandler(handler)

# Attach our Stackdriver formatter to the app's logger
handler = logging.StreamHandler()
handler.setFormatter(StackdriverJsonFormatter())
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)

For Django Apps:

Update your settings.py logging config:

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "stackdriver": {
            "()": "stackdriver_logging.StackdriverJsonFormatter",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "stackdriver",
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "INFO",
        "propagate": False,
    },
}

Step 5: Verify the Setup

Start Gunicorn with your config:

gunicorn --config gunicorn.conf.py your_app:app

Check your logs—you should see single, valid JSON entries for each log, with fields Stackdriver recognizes (severity, timestamp, etc.), no duplicates.

Key Dependencies

Install the required packages first:

pip install python-json-logger iso8601

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:28:07