"""Logging. The logging library provides the handler that sends the logs to a regular HTTPS provider (in our case we use Loggly). The message is also formatted in a specific format (json), which includes fields that are specific to the Orchard, such as the service name, the correlation id. """ import datetime import logging import logging.handlers import traceback from requests_futures.sessions import FuturesSession from grass import config session = FuturesSession() def callback(session, resp): """Post callback. When the post has been dispatched to loggly, handle the response (in our case we don't have anything to do). Args: session (FutureSession): the session that triggered the request. resp (object): the response of the request to the service. """ pass class DSNHandler(logging.Handler): """DNSHandler.""" def __init__(self, dsn): """Constructor of HTTPSHandler. Args: dsn (str): the full dsn to send the log to. Returns: HTTPSHandler: the created handler. """ logging.Handler.__init__(self) self.dsn = dsn def get_full_message(self, record): """Get the full message for a record. Some of our systems might still require this data to be sent to loggly, so instead of sending the message of the record, we send the exception information. Args: record (LogRecord): the record to log. Returns: mixed: the string or an object (dictionary). """ if record.exc_info: return '\n'.join(traceback.format_exception(*record.exc_info)) else: return record.msg def emit(self, record): """Emit a record. From the documentation: do whatever it takes to actually log the specified logging record. Here: we send it to the provider. The payload matches Orchard format. Args: record (LogRecord): the record to log. """ try: payload = { 'tag': 'ows1', 'timestamp': datetime.datetime.fromtimestamp( record.created ).isoformat(), 'level': record.levelname, 'correlation_id': record.correlation_id, 'message': self.get_full_message(record), 'service': config.SERVICE_NAME, 'service_version': config.SERVICE_VERSION, 'environment': config.environment, 'meta': { 'file_name': record.filename, 'function_name': record.funcName, 'line': record.lineno, }, } session.post(self.dsn, json=payload, background_callback=callback) except (KeyboardInterrupt, SystemExit): raise except: self.handleError(record) class CustomAdapter(logging.LoggerAdapter): """Custom logger adapter. This adapter expects the passed in dict-like object to have a 'correlation_id' key, whose value is prepended to the log message. """ def process(self, msg, kwargs): kwargs['extra'] = self.extra return '[%s] %s' % (self.extra.get('correlation_id', ''), msg), kwargs