59 lines
No EOL
1.7 KiB
Python
59 lines
No EOL
1.7 KiB
Python
"""Gunicorn configuration.
|
|
|
|
All Gunicorn settings live here instead of the Dockerfile CMD so they can be
|
|
tweaked without rebuilding the image, and so the access-log format/timezone are
|
|
defined in one place.
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
from datetime import datetime, timezone
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from gunicorn.glogging import Logger
|
|
|
|
|
|
|
|
TIMEZONE = os.environ.get("TZ", "UTC")
|
|
|
|
|
|
class CustomLogger(Logger):
|
|
"""Gunicorn logger that emits timezone-aware ISO timestamps."""
|
|
|
|
def atoms(self, resp, req, environ, request_time):
|
|
"""Build the log atoms, overriding the time atom with a custom format."""
|
|
atoms = super().atoms(resp, req, environ, request_time)
|
|
try:
|
|
now = datetime.now(ZoneInfo(TIMEZONE))
|
|
except Exception:
|
|
now = datetime.now(timezone.utc)
|
|
# [09/Aug/2026:17:42:48 +0000] -> [2026-08-09 20:41:00 +0300]
|
|
atoms["t"] = now.strftime('%Y-%m-%d %H:%M:%S %z')
|
|
return atoms
|
|
|
|
|
|
# ---- Server ----
|
|
bind = "0.0.0.0:8000"
|
|
workers = 2
|
|
|
|
# ---- Logging ----
|
|
accesslog = "-"
|
|
errorlog = "-"
|
|
capture_output = True
|
|
loglevel = "info"
|
|
|
|
# %(h)s = real client IP. Gunicorn resolves X-Forwarded-For based on its built-in
|
|
# proxy trust logic (the same mechanism already validated behind Traefik).
|
|
# %(t)s = overridden by CustomLogger -> [2026-08-09 20:41:00 +0300]
|
|
access_log_format = '%(h)s - - [%(t)s] "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
|
# logger_class = "gunicorn.conf:CustomLogger"
|
|
|
|
logger_class = CustomLogger
|
|
|
|
# ---- Timezone for Python logging / error logs ----
|
|
# Default to UTC unless TZ is set (e.g. TZ=Africa/Nairobi at docker run time).
|
|
os.environ.setdefault("TZ", os.environ.get("TZ", "UTC"))
|
|
try:
|
|
time.tzset()
|
|
except AttributeError: # non-Unix
|
|
pass |