« Back to Index

Python: Dynamic Log Levels via Abstraction

View original Gist on GitHub

Tags: #python #logs

1. Python Dynamic Log Levels via Abstraction.py

import logging

def log_it(msg, level='error'):
    fn = getattr(logging, level)
    fn(msg)

log_it(1, level='warning')
# WARNING:root:1

log_it(1, level='error')
log_it(1)
# ERROR:root:1

2. More nested abstractions.py

import inspect

import logging
import structlog

# TODO: configuration of structure logs (see my other gists)

def extract_status_code(exc):
    return exc.response['ResponseMetadata']['HTTPStatusCode'] if hasattr(exc, 'response') else 500


def get_function_name(steps=2):
    """
    The 'steps' indicate how far back in the stack to traverse.

    Example A...

    0: get_function_name()
    1: log_it()
    2: whatever called log_it()

    Example B...

    0: get_function_name()
    1: log_it()
    2. log_exception()
    3: whatever called log_exception()
    
    It gets tricky when you have a function nested inside of another function
    and even more tricky when that nested function is called at different
    levels.
    """
    return inspect.stack()[steps].function


def log_it(msg, steps=2, level='error', **kwargs):
    """Abstraction to make logging generic information easier to manage."""
    
    log_level = getattr(logger, level)
    log_level(msg, fn=get_function_name(steps=steps), **kwargs)


def log_exception(exc, msg='exception caught', steps=3, **kwargs):
    """AWS exceptions don't always have a code. So we protect against that."""
    
    has_code = hasattr(exc, 'code')

    exc_data = {'exc_class': type(exc),
                'exc_type': exc.__class__.__name__,
                'exc_msg': str(exc),
                'err_code': exc.code if has_code else extract_status_code(exc)}

    log_it(msg, steps=steps, **exc_data, **kwargs)
    
"""
Alternative version that moves some things around to accommodate needing to record metrics
and not wanting to have to look back up the stack trace multiple times...

def log_it(message, steps=2, level='error', fn=False, metric_name='log', **kwargs):
    """Abstraction to make general instrumentation (logs/metrics) easier to manage."""

    if not fn:
        # log_exception will always provide a fn (function name)
        # meaning log_it was called directly
        fn = get_function_name(steps=steps)

    metrics.increment(metric_name, tags={'msg': message, 'level': level, 'func': fn, **kwargs})

    # prevent log_level call with func keyword argument from exploding
    # as func will sometimes be provided via kwargs object
    kwargs.pop('func', None)

    log_level = getattr(logger, level)
    log_level(message, func=fn, **kwargs)


def log_exception(exc, msg='exception caught', trigger_type='expected', **kwargs):
    """Abstraction to make exception instrumentation easier to manage."""

    fn = get_function_name(steps=2)

    has_code = hasattr(exc, 'code')
    err_code = exc.code if has_code else extract_status_code(exc)

    exc_data = {'exc_class': type(exc),
                'exc_type': exc.__class__.__name__,
                'exc_msg': str(exc),
                'err_code': err_code}

    metric_tags = {'type': trigger_type,
                   'func': fn,
                   'msg': msg,
                   **exc_data,
                   **kwargs}

    log_it(msg, fn=fn, metric_name='exception', **metric_tags)
"""

Tornado Unhandled Exception Function.py

"""
The log abstractions can be used along with tornado to make tracking unhandled exceptions easier
"""

class AuthBaseHandler(BaseHandler):
    def write_error(self, status_code, **kwargs):
        if kwargs.get('exc_info'):
            # we're dealing with an unhandled exception

            stack_trace = '\n'.join([line for line in traceback.format_exception(*kwargs['exc_info'])])
            exc = kwargs['exc_info'][1]
            log_exception(exc, msg='unhandled exception', trigger_type='unexpected', stack=stack_trace)

            self.set_status(status_code)
            self.write({'state': 'error',
                        'code': status_code,
                        'message': 'unknown exception'})