Tune Gunicorn log output

- keep access logs on stdout/stderr
- filter static, Socket.IO, and boot noise
- align Gunicorn rows with app log format
This commit is contained in:
Antti Kettunen 2026-04-21 18:00:29 +03:00
parent 9619a63e4e
commit 8b9284f414
No known key found for this signature in database
GPG key ID: C6B2A3D250359BD7
3 changed files with 81 additions and 2 deletions

View file

@ -11,7 +11,9 @@ timeout = 120
# Keep shutdowns under Docker's stop window so container restarts stay graceful. # Keep shutdowns under Docker's stop window so container restarts stay graceful.
graceful_timeout = 8 graceful_timeout = 8
# Logging goes to stdout/stderr so Docker can collect it. # Logging goes to stdout/stderr and is filtered by the custom logger class.
accesslog = "-" accesslog = "-"
errorlog = "-" errorlog = "-"
access_log_format = '%(h)s - - "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
loglevel = "info" loglevel = "info"
logger_class = "utils.gunicorn_logger.FilteredGunicornLogger"

View file

@ -13,7 +13,10 @@ timeout = 120
# Don't let local reloads wait too long for shutdown. # Don't let local reloads wait too long for shutdown.
graceful_timeout = 1 graceful_timeout = 1
# Logging goes to stdout/stderr so the shell launcher can collect it. # Logging goes to stdout/stderr and is filtered by the custom logger class.
accesslog = "-" accesslog = "-"
errorlog = "-" errorlog = "-"
# Mimic process log format
access_log_format = '%(h)s - - "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
loglevel = "info" loglevel = "info"
logger_class = "utils.gunicorn_logger.FilteredGunicornLogger"

74
utils/gunicorn_logger.py Normal file
View file

@ -0,0 +1,74 @@
"""Gunicorn logger tweaks for SoulSync."""
from __future__ import annotations
import logging
from gunicorn.glogging import Logger as GunicornLogger
from utils.logging_config import ColoredFormatter
class FilteredGunicornLogger(GunicornLogger):
"""Gunicorn logger that skips noisy static and Socket.IO access logs."""
_STATIC_PREFIXES = (
"/static/",
"/assets/",
"/socket.io",
"/favicon.ico",
"/robots.txt",
)
_STATIC_SUFFIXES = (
".css",
".js",
".map",
".png",
".jpg",
".jpeg",
".gif",
".svg",
".ico",
".woff",
".woff2",
".ttf",
".eot",
)
def _should_skip_access_log(self, environ) -> bool:
path = environ.get("PATH_INFO") or ""
if not path:
return False
normalized = path if path.startswith("/") else f"/{path}"
lower_path = normalized.lower()
if any(
lower_path == prefix.rstrip("/") or lower_path.startswith(prefix)
for prefix in self._STATIC_PREFIXES
):
return True
return any(lower_path.endswith(suffix) for suffix in self._STATIC_SUFFIXES)
def access(self, resp, req, environ, request_time):
if self._should_skip_access_log(environ):
return
super().access(resp, req, environ, request_time)
def setup(self, cfg):
super().setup(cfg)
app_like_formatter = ColoredFormatter(
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
error_level = getattr(logging, cfg.loglevel.upper(), logging.INFO)
for handler in self.access_log.handlers:
handler.setFormatter(app_like_formatter)
handler.setLevel(logging.INFO)
for handler in self.error_log.handlers:
handler.setFormatter(app_like_formatter)
handler.setLevel(error_level)