Skip to content

Instantly share code, notes, and snippets.

@danielrichman
Last active December 16, 2020 19:55
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielrichman/1acbbd66166ab857c404 to your computer and use it in GitHub Desktop.
Save danielrichman/1acbbd66166ab857c404 to your computer and use it in GitHub Desktop.
logger adapters
class FlaskLoggerAdapter(logging.LoggerAdapter):
"""
A :class:`logging.LoggerAdapter` that adds request context
Adds the following attributes to log records (which can then be used in a
:class:`logging.Formatter` string).
* base_url: ``flask.request.base_url``
* flask_endpoint: ``flask.request.endpoint``
* remote_addr: ``flask.request.remote_addr``
* session_id: ``flask.session["session_id"]``
* user_id: ``flask.session["user_id"]``
Relevant properties will be None, if there is no request context,
or the session is bad.
"""
def __init__(self, logger):
super(LoggerAdapter, self).__init__(logger, None)
def process(self, msg, kwargs):
try:
add = {}
if flask.has_request_context():
add["request_path"] = flask.request.path
add["flask_endpoint"] = flask.request.endpoint
add["remote_addr"] = flask.request.remote_addr
# Example:
# if getattr(flask.session, "ok", False):
# add["session_id"] = flask.session["session_id"]
# add["user_id"] = flask.session["user_id"]
extra = kwargs.setdefault("extra", {})
for key in add: extra.setdefault(key, add[key])
except Exception as e:
traceback.print_exc()
return msg, kwargs
class OptionalKeysFormatter(logging.Formatter):
"""
Adapt LogRecords that haven't come from :class:`LoggerAdapter`
This is a :class:`logging.Formatter` that fills in the keys that
:class:`LoggerAdapter` adds with ``None`` if they are not already set,
so that the same formatter may be used for records from
:class:`LoggerAdapter` and regular loggers.
"""
fill_keys = ("request_path", "flask_endpoint", "remote_addr")
# Example: "session_id", "user_id")
def format(self, record):
for key in self.fill_keys:
if not hasattr(record, key):
setattr(record, key, None)
return super(OptionalKeysFormatter, self).format(record)
def getLogger(name):
""":func:`logging.getLogger`, but returns :class:`LoggerAdapter` objects"""
return LoggerAdapter(logging.getLogger(name))
# Example:
_format_string = "%(name)s %(levelname)s " \
"(%(remote_addr)s %(flask_endpoint)s " \
"%(session_id)s %(user_id)s) %(message)s"
_format_email = \
"""%(levelname)s from logger %(name)s
Time: %(asctime)s
Location: %(pathname)s:%(lineno)d
Module: %(module)s
Function: %(funcName)s
Request: %(request_path)s
Endpoint: %(flask_endpoint)s
Client: %(remote_addr)s
Session ID: %(session_id)s
User ID: %(user_id)s
%(message)s"""
class LoggingContext:
"""
A :class:`LoggingContext` provides a method of setting attributes inside
a `with` block (setting the context), like so::
with context.set(job=5):
assert context.job == 5
with context.set(crsid="djr61"):
assert (context.job, context.crsid) == (5, "djr61")
assert context.crsid is None
Create a single context, and pass it to your (potentially many)
:class:`LoggerAdapter`s to add the attributes to records.
"""
def __init__(self):
self.crsid = None
self.job = None
self.vm = None
class _Keep: pass
Keep = _Keep()
@contextlib.contextmanager
def set(self, crsid=Keep, job=Keep, vm=Keep):
old = (self.crsid, self.job, self.vm)
if crsid is not self.Keep: self.crsid = crsid
if job is not self.Keep: self.job = job
if vm is not self.Keep: self.vm = vm
yield
self.crsid, self.job, self.vm = old
class LoggerAdapter(logging.LoggerAdapter):
"""
A :class:`logging.LoggerAdapter` that adds CRSID, Job and VM attributes
to log records.
"""
def __init__(self, context, logger):
super(LoggerAdapter, self).__init__(logger, None)
self.context = context
def process(self, msg, kwargs):
try:
extra = kwargs.setdefault("extra", {})
extra["crsid"] = self.context.crsid
extra["job"] = self.context.job
extra["vm"] = self.context.vm
except Exception as e:
traceback.print_exc()
return msg, kwargs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment