Skip to content

Commit d62f227

Browse files
committed
Add structlog support for gunicorn
1 parent 7a94628 commit d62f227

File tree

2 files changed

+91
-10
lines changed

2 files changed

+91
-10
lines changed

config/django/base.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,6 @@
8383

8484
ROOT_URLCONF = "config.urls"
8585

86-
print(os.path.join(APPS_DIR, "templates"))
87-
8886
TEMPLATES = [
8987
{
9088
"BACKEND": "django.template.backends.django.DjangoTemplates",

gunicorn.conf.py

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,91 @@
1-
# If you are not having memory issues, just delete this.
2-
# This is primarily to prevent memory leaks
3-
# Based on https://devcenter.heroku.com/articles/python-gunicorn
4-
# Based on https://adamj.eu/tech/2019/09/19/working-around-memory-leaks-in-your-django-app/
5-
# https://docs.gunicorn.org/en/latest/settings.html#max-requests
6-
# https://docs.gunicorn.org/en/latest/settings.html#max-requests-jitter
7-
max_requests = 1200
8-
max_requests_jitter = 100
1+
# https://mattsegal.dev/django-gunicorn-nginx-logging.html
2+
# https://albersdevelopment.net/2019/08/15/using-structlog-with-gunicorn/
3+
4+
import logging
5+
import logging.config
6+
import re
7+
8+
import structlog
9+
10+
11+
def combined_logformat(logger, name, event_dict):
12+
if event_dict.get("logger") == "gunicorn.access":
13+
message = event_dict["event"]
14+
15+
parts = [
16+
r"(?P<host>\S+)", # host %h
17+
r"\S+", # indent %l (unused)
18+
r"(?P<user>\S+)", # user %u
19+
r"\[(?P<time>.+)\]", # time %t
20+
r'"(?P<request>.+)"', # request "%r"
21+
r"(?P<status>[0-9]+)", # status %>s
22+
r"(?P<size>\S+)", # size %b (careful, can be '-')
23+
r'"(?P<referer>.*)"', # referer "%{Referer}i"
24+
r'"(?P<agent>.*)"', # user agent "%{User-agent}i"
25+
]
26+
pattern = re.compile(r"\s+".join(parts) + r"\s*\Z")
27+
m = pattern.match(message)
28+
res = m.groupdict()
29+
30+
if res["user"] == "-":
31+
res["user"] = None
32+
33+
res["status"] = int(res["status"])
34+
35+
if res["size"] == "-":
36+
res["size"] = 0
37+
else:
38+
res["size"] = int(res["size"])
39+
40+
if res["referer"] == "-":
41+
res["referer"] = None
42+
43+
event_dict.update(res)
44+
45+
return event_dict
46+
47+
48+
timestamper = structlog.processors.TimeStamper(fmt="iso", utc=True)
49+
pre_chain = [
50+
# Add the log level and a timestamp to the event_dict if the log entry
51+
# is not from structlog.
52+
structlog.stdlib.add_log_level,
53+
structlog.stdlib.add_logger_name,
54+
timestamper,
55+
combined_logformat,
56+
]
57+
58+
CONFIG_DEFAULTS = {
59+
"version": 1,
60+
"disable_existing_loggers": False,
61+
"root": {"level": "INFO", "handlers": ["default"]},
62+
"loggers": {
63+
"gunicorn.error": {"level": "INFO", "handlers": ["default"], "propagate": False, "qualname": "gunicorn.error"},
64+
"gunicorn.access": {
65+
"level": "INFO",
66+
"handlers": ["default"],
67+
"propagate": False,
68+
"qualname": "gunicorn.access",
69+
},
70+
"django_structlog": {
71+
"level": "INFO",
72+
"handlers": [],
73+
"propagate": False,
74+
},
75+
},
76+
"handlers": {
77+
"default": {
78+
"class": "logging.StreamHandler",
79+
"formatter": "json_formatter",
80+
},
81+
},
82+
"formatters": {
83+
"json_formatter": {
84+
"()": structlog.stdlib.ProcessorFormatter,
85+
"processor": structlog.processors.JSONRenderer(),
86+
"foreign_pre_chain": pre_chain,
87+
}
88+
},
89+
}
90+
91+
logging.config.dictConfig(CONFIG_DEFAULTS)

0 commit comments

Comments
 (0)