|
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