Skip to content

Instantly share code, notes, and snippets.

@bukowa
Last active February 11, 2023 06:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bukowa/f54a7be4c3186275262bf6080fee2d50 to your computer and use it in GitHub Desktop.
Save bukowa/f54a7be4c3186275262bf6080fee2d50 to your computer and use it in GitHub Desktop.
django + gunicorn proper logging setup
from .logging import GunicornLogger, gunicorn_logconfig_dict, gunicorn_access_log_format
wsgi_app = "conf.wsgi"
bind = "0.0.0.0:8000"
workers = 2
proxy_protocol = True
proxy_allow_ips = "*"
forwarded_allow_ips = "*"
logger_class = GunicornLogger
logconfig_dict = gunicorn_logconfig_dict
access_log_format = gunicorn_access_log_format
2023-02-11 07:01:16:763941: 102023 INFO gunicorn.error info 139980609957952 Starting gunicorn 20.1.0
2023-02-11 07:01:16:764141: 102023 INFO gunicorn.error info 139980609957952 Listening at: http://0.0.0.0:8000 (102023)
2023-02-11 07:01:16:764170: 102023 INFO gunicorn.error info 139980609957952 Using worker: sync
2023-02-11 07:01:16:765599: 102024 INFO gunicorn.error info 139980609957952 Booting worker with pid: 102024
2023-02-11 07:01:16:818548: 102025 INFO gunicorn.error info 139980609957952 Booting worker with pid: 102025
2023-02-11 07:01:21:466149: 102025 WARNING django.request log_response 139980609957952 Not Found: /is_alive
2023-02-11 07:01:21:466396: 102025 INFO gunicorn.access access 139980609957952 127.0.0.1 - -"GET /is_alive HTTP/1.1" 404 179 "-"
#
#
# https://i.stack.imgur.com/dtg8O.png
#
#
import logging
import os
from datetime import datetime
from logging import Formatter as _Formatter
from gunicorn.glogging import Logger as _GunicornLogger
django_level = os.environ.get("DJANGO_LOGGING_LEVEL", "INFO").upper()
gunicorn_level = os.environ.get("GUNICORN_LOGGING_LEVEL", "INFO").upper()
gunicorn_access_level = os.environ.get("GUNICORN_ACCESS_LOGGING_LEVEL", "WARNING").upper()
log_datefmt = "%Y-%m-%d %H:%M:%S:%f:%z"
log_format = "%(asctime)s %(process)d %(levelname)s %(name)s %(funcName)s %(thread)d %(message)s"
gunicorn_access_log_format = '%(h)s %(l)s %(u)s"%(r)s" %(s)s %(b)s "%(f)s"'
class Formatter(_Formatter):
def formatTime(self, record, *args, **kwargs) -> str:
"""
Python default does not support microseconds in `asctime`.
"""
return datetime.fromtimestamp(record.created).strftime(log_datefmt)
class GunicornLogger(_GunicornLogger):
def now(self):
"""
Gunicorn uses this method to log the current time in the access log.
"""
return datetime.now().strftime(log_datefmt)
base_logconfig_dict = {
"version": 1,
"formatters": {
"custom": {
'()': Formatter,
"format": log_format,
"datefmt": log_datefmt,
},
},
"handlers": {
"stdout": {
"level": 0,
"class": "logging.StreamHandler",
"formatter": "custom",
"stream": "ext://sys.stdout",
},
"stderr": {
"level": 0,
"class": "logging.StreamHandler",
"formatter": "custom",
"stream": "ext://sys.stderr",
},
},
}
gunicorn_logconfig_dict = {
**base_logconfig_dict,
# required to avoid error in gunicorn 20.1.0:
# `Error: Unable to configure root logger`
"root": {},
"loggers": {
# gunicorn access logger logs in one place and on `INFO` level by default;
# note: things that comes to my mind
# when it should be enabled, is when;
# a) something is bypassing proxy and should be logged
# b) we are debugging
"gunicorn.access": {
"handlers": ["stderr"],
"level": gunicorn_access_level,
"propagate": False,
},
"gunicorn.error": {
"handlers": ["stderr"],
"level": gunicorn_level,
"propagate": False,
},
},
"disable_existing_loggers": True,
}
django_logconfig_dict = {
**base_logconfig_dict,
# all loggers not specified in `loggers`
# will use the settings from `root` logger
"root": {
"handlers": ["stderr"],
"level": django_level,
},
# override loggers that django uses
# in DEFAULT_LOGGING config dict
"loggers": {
# do not propagate to `root` logger
"django": {
"handlers": ["stderr"],
"level": django_level,
"propagate": False,
},
# propagate to `django` logger
"django.server": {
"handlers": [],
"level": django_level,
"propagate": True,
},
"faker": {
"level": logging.INFO,
"propagate": False,
},
},
"disable_existing_loggers": False,
}
from .logging import django_logconfig_dict
LOGGING = django_logconfig_dict
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment