sprockets.logging/sprockets/logging.py
Gavin M. Roy b948eef467 Improved logging functionality
- Monkeypatch the logging.currentframe to make it smarter, better
- Don't sort the JSON
- Add logging messages and if they're set, then don't include the request attribute in the JSON log data
2015-06-23 17:48:34 -04:00

143 lines
4.6 KiB
Python

"""
Make good log output easier.
- :class:`ContextFilter` adds fixed properties to a log record
- :class:`JSONRequestFormatter` formats log records as JSON output
- :method:`tornado_log_function` is for use as the
:class`tornado.web.Application.log_function` in conjunction with
:class:`JSONRequestFormatter` to output log lines as JSON.
"""
from __future__ import absolute_import
from logging import config
import json
import logging
import sys
try:
from tornado import log
except ImportError:
log = None
version_info = (1, 2, 0)
__version__ = '.'.join(str(v) for v in version_info)
# Shortcut methods and constants to avoid needing to import logging directly
dictConfig = config.dictConfig
getLogger = logging.getLogger
DEBUG = logging.DEBUG
INFO = logging.INFO
WARN = logging.WARN
WARNING = logging.WARNING
ERROR = logging.ERROR
class ContextFilter(logging.Filter):
"""
Ensures that properties exist on a LogRecord.
:param list|None properties: optional list of properties that
will be added to LogRecord instances if they are missing
This filter implementation will ensure that a set of properties
exists on every log record which means that you can always refer
to custom properties in a format string. Without this, referring
to a property that is not explicitly passed in will result in an
ugly ``KeyError`` exception.
"""
def __init__(self, name='', properties=None):
logging.Filter.__init__(self, name)
self.properties = list(properties) if properties else []
def filter(self, record):
for property_name in self.properties:
if not hasattr(record, property_name):
setattr(record, property_name, None)
return True
class JSONRequestFormatter(logging.Formatter):
"""Instead of spitting out a "human readable" log line, this outputs
the log data as JSON.
"""
def format(self, record):
"""Return the log data as JSON
:param record logging.LogRecord: The record to format
:rtype: str
"""
output = {'name': record.name,
'module': record.module,
'message': record.msg % record.args,
'level': logging.getLevelName(record.levelno),
'line_number': record.lineno,
'process': record.processName,
'timestamp': self.formatTime(record),
'thread': record.threadName,
'file': record.filename,
'request': record.args}
for key, value in list(output.items()):
if not value:
del output[key]
if 'message' in output:
del output['request']
return json.dumps(output)
def tornado_log_function(handler):
"""Assigned when creating a :py:class:`tornado.web.Application` instance
by passing the method as the ``log_function`` argument:
.. code:: python
app = tornado.web.Application([('/', RequestHandler)],
log_function=tornado_log_function)
:type handler: :py:class:`tornado.web.RequestHandler`
"""
status_code = handler.get_status()
if status_code < 400:
log_method = log.access_log.info
elif status_code < 500:
log_method = log.access_log.warning
else:
log_method = log.access_log.error
correlation_id = (getattr(handler, 'correlation_id', None) or
handler.request.headers.get('Correlation-ID', None))
log_method('', {'correlation_id': correlation_id,
'duration': 1000.0 * handler.request.request_time(),
'headers': handler.request.headers,
'method': handler.request.method,
'path': handler.request.path,
'protocol': handler.request.protocol,
'query_args': handler.request.query_arguments,
'remote_ip': handler.request.remote_ip,
'status_code': status_code})
def currentframe():
"""Return the frame object for the caller's stack frame."""
try:
raise Exception
except:
traceback = sys.exc_info()[2]
frame = traceback.tb_frame
while True:
if hasattr(frame, 'f_code'):
filename = frame.f_code.co_filename
if filename.endswith('logging.py') or \
filename.endswith('logging/__init__.py'):
frame = frame.f_back
continue
return frame
return traceback.tb_frame.f_back
# Monkey-patch currentframe
logging.currentframe = currentframe