Last active
October 4, 2018 12:03
-
-
Save Integralist/1140bcd773616ecdae9bb4d2e9e55b34 to your computer and use it in GitHub Desktop.
[Python Dynamic Log Levels via Abstraction] #python #logging #logs #dynamic #levels
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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'}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment