業務中要求,python項目的日誌輸出爲json串,同時包括異常;通過查看python logging相關的源碼,發現還不能徹底的兼容;好比異常的源碼那裏:html
class Formatter(object): """省略""" def format(self, record): record.message = record.getMessage() if self.usesTime(): record.asctime = self.formatTime(record, self.datefmt) s = self.formatMessage(record) if record.exc_info: # Cache the traceback text to avoid converting it multiple times # (it's constant anyway) if not record.exc_text: record.exc_text = self.formatException(record.exc_info) if record.exc_text: if s[-1:] != "\n": s = s + "\n" s = s + record.exc_text if record.stack_info: if s[-1:] != "\n": s = s + "\n" s = s + self.formatStack(record.stack_info) return s
logging.Formatter的format方法,首先會按照格式化串格式化message,而後若是出現異常,是直接再message後面加上異常;此時格式已經不是指定的格式,所以這裏須要修自定義。python
# -*- coding:utf-8 -*- import json import logging import os import traceback BASE_DIR = os.path.abspath(os.getcwd()) LOG_DIR = os.path.join(BASE_DIR, "logs") host_ip ="localhost" JSON_LOGGING_FORMAT = json.dumps({ "ip": "%(ip)s", "app": "%(app)s", "level": "%(levelname)s", "trace": "%(stack_msg)s", "filepath": "%(pathname)s", "line_number": "%(lineno)s", "time": "%(asctime)s", "message": "%(message)s", "stack_trace": "%(exc_text)s" }) class JsonLoggingFilter(logging.Filter): def __init__(self, name, ip, app): logging.Filter.__init__(self, name=name) self.ip = ip self.app = app def filter(self, record): record.ip = self.ip record.app = self.app # 爲record 添加異常堆棧信息字段; 當有多個handler 的時候,這裏會判斷屢次 if hasattr(record, "stack_msg") and hasattr(record, "stack_trace"): return True if record.exc_info: ex_type, ex_val, ex_stack = record.exc_info stack_list = [] for stack in traceback.extract_tb(ex_stack): stack_list.append("%s" % stack) record.stack_msg = ex_val record.stack_trace = "#".join(stack_list) else: record.stack_msg, record.stack_trace = "", "" return True class JsonFormatter(logging.Formatter): def __init__(self, fmt=None): logging.Formatter.__init__(self, fmt=fmt) def format(self, record): record.message = record.getMessage() if self.usesTime(): record.asctime = self.formatTime(record, self.datefmt) if record.exc_info: # Cache the traceback text to avoid converting it multiple times # (it's constant anyway) if not record.exc_text: record.exc_text = self.formatException(record.exc_info).replace("\n", " ").replace("\"", "'") s = self.formatMessage(record) return s class JsonLogger(logging.Logger): logger = None level = None mode = None def __init__(self, app_name, level=logging.DEBUG, console_level=logging.INFO, mode="w"): self.name = app_name self.app_name = app_name logging.Logger.__init__(self, name=app_name) self.logger = logging.Logger(name=app_name) self.logger.setLevel(level) if not os.path.exists(LOG_DIR): os.makedirs(LOG_DIR) log_file_path = os.path.join(LOG_DIR, "%s.json" % app_name) json_logging_filter = JsonLoggingFilter(app_name, ip=host_ip, app=app_name) json_formatter = JsonFormatter(JSON_LOGGING_FORMAT) # 文件日誌 file_handle = logging.FileHandler(log_file_path, mode=mode) file_handle.setLevel(level) file_handle.setFormatter(json_formatter) file_handle.addFilter(json_logging_filter) # 控制檯日誌 console_handle = logging.StreamHandler() console_handle.setLevel(console_level) console_handle.setFormatter(json_formatter) console_handle.addFilter(json_logging_filter) self.logger.addHandler(file_handle) self.logger.addHandler(console_handle) def getLogger(self): return self.logger def setLevel(self, level): self.logger.level = level if __name__ == '__main__': my_logger = JsonLogger("python-common").getLogger() my_logger.info("info level log") try: open('/path/to/does/not/exist', 'rb') except FileNotFoundError as e: my_logger.exception("file exception", exc_info=e)
參考:json
http://www.javashuo.com/article/p-wqlkttny-y.htmlapp
關於異常 https://www.jianshu.com/p/b342b19657fcspa
日誌輸出 json串 https://blog.csdn.net/diqiuyi7777/article/details/86498203.net