Normalerweise verwende ich hauptsächlich Python, aber strukturelle Daten sind bequemer, wenn eine Plattform zur Protokollsammlung / -analyse mit einem Cloud-Dienst entwickelt wird, nicht wahr? Wenn Sie jedoch den normalen Formatierer von Logging verwenden, bei dem es sich um eine Standard-Python-Bibliothek handelt, müssen Sie ihn einmal in Strukturdaten konvertieren. Es ist nicht gut, eine andere Anwendung nur für die Konvertierung zu erstellen. Daher habe ich einen Formatierer entwickelt, der Protokolle in JSON ausgibt.
--OS (garantierter Betrieb) - MacOS Catalina - Ubuntu 18.04 --Sprache - Python ^3.7
--Paket-Manager - Poetry
Bibliothek
Nur Standardbibliothek
Entwicklungsbibliothek
Der entwickelte Formatierer ist unter homoluctus / json-pyformatter zu finden. Es wird auch auf PyPI-> [json-pyformatter 0.1.0] veröffentlicht (https://pypi.org/project/json-pyformatter/).
pip install json-pyformatter
Das Feld, das ausgegeben werden kann, ist logrecord-attribute der Standardbibliothek Logging. Die vom entwickelten Formatierer ausgegebenen Standardfelder sind "asctime", "levelname" und "message".
import logging
from json_pyformmatter import JsonFormatter
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
fields = ('levelname', 'filename', 'message')
formatter = JsonFormatter(fields=fields)
handler.setFormatter(formatter)
logger.addHandler(hander)
logger.info('hello')
Wenn dies ausgeführt wird, wird das JSON-Protokoll wie unten gezeigt ausgegeben.
{"levelname": "INFO", "filename": "test_formatter.py", "message": "hello"}
Außerdem ist es einfacher zu erkennen, ob Sie als Argument von JsonFormatter "indent = 2" angeben.
{
"levelname": "INFO",
"filename": "test_formatter.py",
"message": "hello"
}
Natürlich können Sie auch Traceback ausgeben. Da es schwierig ist zu erkennen, ob es sich um eine Zeile handelt, ist sie in einem Array angeordnet.
{
'asctime': '2019-12-01 13:58:34',
'levelname': 'ERROR',
'message': 'error occurred !!',
'traceback': [
'Traceback (most rec...ll last):',
'File "/example/test..._exc_info',
'raise TypeError(message)',
'TypeError: error occurred !!'
]
}
Von hier aus werde ich den Quellcode erklären.
Ich werde vorerst den gesamten Quellcode schreiben.
import json
from collections import OrderedDict
from logging import Formatter
class JsonFormatter(Formatter):
default_fields = ('asctime', 'levelname', 'message')
def __init__(self, fields=None, datefmt=None, indent=None):
"""
Args:
fields (tuple, list)
datefmt (str)
indent (str, int)
"""
self.fields = (
self.get_or_none(fields, (list, tuple)) or self.default_fields
)
# default time format is %Y-%m-%d %H:%M:%S
self.datefmt = (
self.get_or_none(datefmt, str) or self.default_time_format
)
self._indent = self.get_or_none(indent, (str, int))
def get_or_none(self, target, types):
"""Check whether target value is expected type.
If target type does not match expected type, returns None.
Args:
target (any)
types (class, tuple)
Returns:
target or None
"""
if isinstance(target, types):
return target
return None
def getMessage(self, record):
if isinstance(record.msg, (list, tuple, dict)):
return record.msg
return record.getMessage()
def _format_json(self, record):
return json.dumps(record, ensure_ascii=False, indent=self._indent)
def _format(self, record):
log = OrderedDict()
try:
for field in self.fields:
log[field] = getattr(record, field)
return log
except AttributeError as err:
raise ValueError(f'Formatting field not found in log record {err}')
def format(self, record):
record.message = self.getMessage(record)
record.asctime = self.formatTime(record, self.datefmt)
formatted_record = self._format(record)
if record.exc_info:
if not record.exc_text:
record.exc_text = self.formatException(record.exc_info)
if record.exc_text:
formatted_record['traceback'] = [
msg.strip() for msg in record.exc_text.strip().split('\n')
]
if record.stack_info:
formatted_record['stack'] = record.stack_info.strip()
return self._format_json(formatted_record)
get_or_none Diese Methode wird beim Erstellen einer Instanz verwendet. Wenn dies nicht der erwartete Typ ist, verwenden Sie den Standardwert oder weisen Sie None so zu, wie er ist.
def get_or_none(self, target, types):
"""Check whether target value is expected type.
If target type does not match expected type, returns None.
Args:
target (any)
types (class, tuple)
Returns:
target or None
"""
if isinstance(target, types):
return target
return None
getMessage
Diese Methode wird verwendet, um die Nachricht des Arguments von logger.info () abzurufen. Der Rückgabewert dieser getMessage wird im JSON-Feld message
festgelegt.
Wenn der Instanztyp von record.msg einer von (list, tuple, dict) ist, wird er so zurückgegeben, wie er ist, andernfalls wird der Rückgabewert der getMessage-Methode der Datensatzinstanz zurückgegeben. Auf diese Weise kann das Protokoll als JSON-Array / -Objekt ausgegeben werden, wenn eines von (Liste, Tupel, Diktat) übergeben wird.
def getMessage(self, record):
if isinstance(record.msg, (list, tuple, dict)):
return record.msg
return record.getMessage()
_format_json Diese Methode gibt den Argumentdatensatz als JSON aus.
def _format_json(self, record):
return json.dumps(record, ensure_ascii=False, indent=self._indent)
_format Eine Methode zum Konvertieren von Datensätzen in ein Python-Wörterbuch. Das vom Benutzer angegebene Feld muss nicht unbedingt im Attribut record enthalten sein, daher handelt es sich um "try-exception". Das Rückgabeprotokoll wird auf "OrderedDict" gesetzt, um die Bestellung zu garantieren.
def _format(self, record):
log = OrderedDict()
try:
for field in self.fields:
log[field] = getattr(record, field)
return log
except AttributeError as err:
raise ValueError(f'Formatting field not found in log record {err}')
format Diese Methode wird von logging.Handler aufgerufen. Ich glaube nicht, dass Sie Ihren eigenen Formatierer erstellen und das Format der übergeordneten Klasse (logging.Formatter) verwenden werden. Überschreiben Sie diese Methode daher unbedingt. Ich formatiere record.exc_text, um Traceback als Array auszugeben. Nach dem Entfernen zusätzlicher Leerzeichen usw. wird es mit einem Zeilenumbruch geteilt, um ein Array zu erstellen. Schließlich entleere ich das Python-Diktat als JSON.
def format(self, record):
record.message = self.getMessage(record)
record.asctime = self.formatTime(record, self.datefmt)
formatted_record = self._format(record)
if record.exc_info:
if not record.exc_text:
record.exc_text = self.formatException(record.exc_info)
if record.exc_text:
formatted_record['traceback'] = [
msg.strip() for msg in record.exc_text.strip().split('\n')
]
if record.stack_info:
formatted_record['stack'] = record.stack_info.strip()
return self._format_json(formatted_record)
Recommended Posts