|
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 | + method, path, version = res["request"].split(" ") |
| 45 | + |
| 46 | + event_dict["method"] = method |
| 47 | + event_dict["path"] = path |
| 48 | + event_dict["version"] = version |
| 49 | + |
| 50 | + return event_dict |
| 51 | + |
| 52 | + |
| 53 | +def gunicorn_event_name_mapper(logger, name, event_dict): |
| 54 | + logger_name = event_dict.get("logger") |
| 55 | + |
| 56 | + if logger_name not in ["gunicorn.error", "gunicorn.access"]: |
| 57 | + return event_dict |
| 58 | + |
| 59 | + GUNICORN_BOOTING = "gunicorn.booting" |
| 60 | + GUNICORN_REQUEST = "gunicorn.request_handling" |
| 61 | + GUNICORN_SIGNAL = "gunicorn.signal_handling" |
| 62 | + |
| 63 | + event = event_dict["event"].lower() |
| 64 | + |
| 65 | + if logger_name == "gunicorn.error": |
| 66 | + event_dict["message"] = event |
| 67 | + |
| 68 | + if event.startswith("starting"): |
| 69 | + event_dict["event"] = GUNICORN_BOOTING |
| 70 | + |
| 71 | + if event.startswith("listening"): |
| 72 | + event_dict["event"] = GUNICORN_BOOTING |
| 73 | + |
| 74 | + if event.startswith("using"): |
| 75 | + event_dict["event"] = GUNICORN_BOOTING |
| 76 | + |
| 77 | + if event.startswith("booting"): |
| 78 | + event_dict["event"] = GUNICORN_BOOTING |
| 79 | + |
| 80 | + if event.startswith("handling signal"): |
| 81 | + event_dict["event"] = GUNICORN_SIGNAL |
| 82 | + |
| 83 | + if logger_name == "gunicorn.access": |
| 84 | + event_dict["event"] = GUNICORN_REQUEST |
| 85 | + |
| 86 | + return event_dict |
| 87 | + |
| 88 | + |
| 89 | +timestamper = structlog.processors.TimeStamper(fmt="iso", utc=True) |
| 90 | +pre_chain = [ |
| 91 | + # Add the log level and a timestamp to the event_dict if the log entry |
| 92 | + # is not from structlog. |
| 93 | + structlog.stdlib.add_log_level, |
| 94 | + structlog.stdlib.add_logger_name, |
| 95 | + timestamper, |
| 96 | + combined_logformat, |
| 97 | + gunicorn_event_name_mapper, |
| 98 | +] |
| 99 | + |
| 100 | +# https://github.com/benoitc/gunicorn/blob/master/gunicorn/glogging.py#L47 |
| 101 | +CONFIG_DEFAULTS = { |
| 102 | + "version": 1, |
| 103 | + "disable_existing_loggers": False, |
| 104 | + "root": {"level": "INFO", "handlers": ["default"]}, |
| 105 | + "loggers": { |
| 106 | + "gunicorn.error": {"level": "INFO", "handlers": ["default"], "propagate": False, "qualname": "gunicorn.error"}, |
| 107 | + "gunicorn.access": { |
| 108 | + "level": "INFO", |
| 109 | + "handlers": ["default"], |
| 110 | + "propagate": False, |
| 111 | + "qualname": "gunicorn.access", |
| 112 | + }, |
| 113 | + "django_structlog": { |
| 114 | + "level": "INFO", |
| 115 | + "handlers": [], |
| 116 | + "propagate": False, |
| 117 | + }, |
| 118 | + }, |
| 119 | + "handlers": { |
| 120 | + "default": { |
| 121 | + "class": "logging.StreamHandler", |
| 122 | + "formatter": "json_formatter", |
| 123 | + }, |
| 124 | + }, |
| 125 | + "formatters": { |
| 126 | + "json_formatter": { |
| 127 | + "()": structlog.stdlib.ProcessorFormatter, |
| 128 | + "processor": structlog.processors.JSONRenderer(), |
| 129 | + "foreign_pre_chain": pre_chain, |
| 130 | + } |
| 131 | + }, |
| 132 | +} |
| 133 | + |
| 134 | +logging.config.dictConfig(CONFIG_DEFAULTS) |
0 commit comments