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:
parent
9619a63e4e
commit
8b9284f414
3 changed files with 81 additions and 2 deletions
|
|
@ -11,7 +11,9 @@ timeout = 120
|
|||
# Keep shutdowns under Docker's stop window so container restarts stay graceful.
|
||||
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 = "-"
|
||||
errorlog = "-"
|
||||
access_log_format = '%(h)s - - "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
||||
loglevel = "info"
|
||||
logger_class = "utils.gunicorn_logger.FilteredGunicornLogger"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ timeout = 120
|
|||
# Don't let local reloads wait too long for shutdown.
|
||||
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 = "-"
|
||||
errorlog = "-"
|
||||
# Mimic process log format
|
||||
access_log_format = '%(h)s - - "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
||||
loglevel = "info"
|
||||
logger_class = "utils.gunicorn_logger.FilteredGunicornLogger"
|
||||
|
|
|
|||
74
utils/gunicorn_logger.py
Normal file
74
utils/gunicorn_logger.py
Normal 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)
|
||||
Loading…
Reference in a new issue