Skip to content

Instantly share code, notes, and snippets.

@airhorns
Created September 13, 2019 12:30
Show Gist options
  • Save airhorns/c2d34b2c823541fc0b32e5c853aab7e7 to your computer and use it in GitHub Desktop.
Save airhorns/c2d34b2c823541fc0b32e5c853aab7e7 to your computer and use it in GitHub Desktop.
Gunicorn structlog integration
import os
import logging.config
import structlog
from .app import app
timestamper = structlog.processors.TimeStamper(fmt="iso")
pre_chain = [
# Add the log level and a timestamp to the event_dict if the log entry is not from structlog.
structlog.stdlib.add_log_level,
timestamper,
]
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"console": {
"()": structlog.stdlib.ProcessorFormatter,
"processor": structlog.dev.ConsoleRenderer(colors=False),
"foreign_pre_chain": pre_chain,
},
"json": {"()": structlog.stdlib.ProcessorFormatter, "processor": structlog.processors.JSONRenderer(), "foreign_pre_chain": pre_chain},
},
"handlers": {
"development": {"level": "DEBUG", "class": "logging.StreamHandler", "formatter": "console"},
"production": {"level": "DEBUG", "class": "logging.StreamHandler", "formatter": "json"},
},
"loggers": {"": {"handlers": [app.config["ENV"]], "level": "DEBUG", "propagate": True}},
}
)
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_log_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.PositionalArgumentsFormatter(),
timestamper,
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
context_class=structlog.threadlocal.wrap_dict(dict),
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
class GunicornLogger(object):
"""
A stripped down version of https://github.com/benoitc/gunicorn/blob/master/gunicorn/glogging.py to provide structlog logging in gunicorn
Modified from http://stevetarver.github.io/2017/05/10/python-falcon-logging.html
"""
def __init__(self, cfg):
self._error_logger = structlog.get_logger("gunicorn.error")
self._error_logger.setLevel(logging.INFO)
self._access_logger = structlog.get_logger("gunicorn.access")
self._access_logger.setLevel(logging.INFO)
self.cfg = cfg
def critical(self, msg, *args, **kwargs) -> None:
self._error_logger.error(msg, *args, **kwargs)
def error(self, msg, *args, **kwargs) -> None:
self._error_logger.error(msg, *args, **kwargs)
def warning(self, msg, *args, **kwargs) -> None:
self._error_logger.warning(msg, *args, **kwargs)
def info(self, msg, *args, **kwargs) -> None:
self._error_logger.info(msg, *args, **kwargs)
def debug(self, msg, *args, **kwargs) -> None:
self._error_logger.debug(msg, *args, **kwargs)
def exception(self, msg, *args, **kwargs) -> None:
self._error_logger.exception(msg, *args, **kwargs)
def log(self, lvl, msg, *args, **kwargs) -> None:
self._error_logger.log(lvl, msg, *args, **kwargs)
def access(self, resp, req, environ, request_time) -> None:
status = resp.status
if isinstance(status, str):
status = status.split(None, 1)[0]
self._access_logger.info(
"request",
method=environ["REQUEST_METHOD"],
request_uri=environ["RAW_URI"],
status=status,
response_length=getattr(resp, "sent", None),
request_time_seconds="%d.%06d" % (request_time.seconds, request_time.microseconds),
pid="<%s>" % os.getpid(),
)
def reopen_files(self) -> None:
pass # we don't support files
def close_on_exec(self) -> None:
pass # we don't support files

Run gunicorn with gunicorn src:app --log-config gunicorn_structlog_setup.py

@omidmaldar
Copy link

Thanks for sharing this. I believe the usage will be gunicorn src:app --logger-class gunicorn_structlog_setup.GunicornLogger
Please see https://docs.gunicorn.org/en/stable/settings.html#logger-class

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment