import io import os import sys import copy import json import socket import logging import traceback from threading import local from collections import OrderedDict from contextlib import contextmanager def json_handler(obj): # if isinstance(obj, (datetime.date, datetime.time)): # return obj.isoformat() return repr(obj) def json_robust_dumps(obj): return json.dumps(obj, default=json_handler) class NiceOrderedDict(OrderedDict): def __str__(self): return json_robust_dumps(self) class SwagFormatter(logging.Formatter): def __init__(self, swaglogger): logging.Formatter.__init__(self, None, '%a %b %d %H:%M:%S %Z %Y') self.swaglogger = swaglogger self.host = socket.gethostname() def format_dict(self, record): record_dict = NiceOrderedDict() if isinstance(record.msg, dict): record_dict['msg'] = record.msg else: try: record_dict['msg'] = record.getMessage() except (ValueError, TypeError): record_dict['msg'] = [record.msg]+record.args record_dict['ctx'] = self.swaglogger.get_ctx() if record.exc_info: record_dict['exc_info'] = self.formatException(record.exc_info) record_dict['level'] = record.levelname record_dict['levelnum'] = record.levelno record_dict['name'] = record.name record_dict['filename'] = record.filename record_dict['lineno'] = record.lineno record_dict['pathname'] = record.pathname record_dict['module'] = record.module record_dict['funcName'] = record.funcName record_dict['host'] = self.host record_dict['process'] = record.process record_dict['thread'] = record.thread record_dict['threadName'] = record.threadName record_dict['created'] = record.created return record_dict def format(self, record): return json_robust_dumps(self.format_dict(record)) class SwagErrorFilter(logging.Filter): def filter(self, record): return record.levelno < logging.ERROR def _tmpfunc(): return 0 def _srcfile(): return os.path.normcase(_tmpfunc.__code__.co_filename) class SwagLogger(logging.Logger): def __init__(self): logging.Logger.__init__(self, "swaglog") self.global_ctx = {} self.log_local = local() self.log_local.ctx = {} def local_ctx(self): try: return self.log_local.ctx except AttributeError: self.log_local.ctx = {} return self.log_local.ctx def get_ctx(self): return dict(self.local_ctx(), **self.global_ctx) @contextmanager def ctx(self, **kwargs): old_ctx = self.local_ctx() self.log_local.ctx = copy.copy(old_ctx) or {} self.log_local.ctx.update(kwargs) try: yield finally: self.log_local.ctx = old_ctx def bind(self, **kwargs): self.local_ctx().update(kwargs) def bind_global(self, **kwargs): self.global_ctx.update(kwargs) def event(self, event_name, *args, **kwargs): evt = NiceOrderedDict() evt['event'] = event_name if args: evt['args'] = args evt.update(kwargs) ctx = self.get_ctx() if ctx: evt['ctx'] = self.get_ctx() if 'error' in kwargs: self.error(evt) else: self.info(evt) def findCaller(self, stack_info=False, stacklevel=1): """ Find the stack frame of the caller so that we can note the source file name, line number and function name. """ f = sys._getframe(3) #On some versions of IronPython, currentframe() returns None if #IronPython isn't run with -X:Frames. if f is not None: f = f.f_back orig_f = f while f and stacklevel > 1: f = f.f_back stacklevel -= 1 if not f: f = orig_f rv = "(unknown file)", 0, "(unknown function)", None while hasattr(f, "f_code"): co = f.f_code filename = os.path.normcase(co.co_filename) if filename == _srcfile: f = f.f_back continue sinfo = None if stack_info: sio = io.StringIO() sio.write('Stack (most recent call last):\n') traceback.print_stack(f, file=sio) sinfo = sio.getvalue() if sinfo[-1] == '\n': sinfo = sinfo[:-1] sio.close() rv = (co.co_filename, f.f_lineno, co.co_name, sinfo) break return rv if __name__ == "__main__": log = SwagLogger() stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setLevel(logging.INFO) stdout_handler.addFilter(SwagErrorFilter()) log.addHandler(stdout_handler) stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setLevel(logging.ERROR) log.addHandler(stderr_handler) log.info("asdasd %s", "a") log.info({'wut': 1}) log.warning("warning") log.error("error") log.critical("critical") log.event("test", x="y") with log.ctx(): stdout_handler.setFormatter(SwagFormatter(log)) stderr_handler.setFormatter(SwagFormatter(log)) log.bind(user="some user") log.info("in req") print("") log.warning("warning") print("") log.error("error") print("") log.critical("critical") print("") log.event("do_req", a=1, b="c")