I tried to develop a Formatter that outputs Python logs in JSON

I usually use Python mainly, but structural data is more convenient when developing a log collection / analysis platform with a cloud service, isn't it? However, if you use Python's standard library Logging's normal Formatter, you have to convert it to structural data once. It's unreasonable to create another application just for conversion. Therefore, I developed a Formatter that outputs logs in JSON.

environment

--OS (guaranteed operation) - MacOS Catalina - Ubuntu 18.04 --Language - Python ^3.7

--Package manager - Poetry

--Development library - Black - Pytest - Flake8

Deliverables

The developed Formatter can be found at homoluctus / json-pyformatter. It is also published on PyPI-> json-pyformatter 0.1.0

how to use

1. Installation

pip install json-pyformatter

2. Example

The field that can be output is logrecord-attributes of the standard library Logging. The default fields output by the developed Formatter are ʻasctime, levelname, 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')

When this is executed, the JSON log will be output as shown below.

{"levelname": "INFO", "filename": "test_formatter.py", "message": "hello"}

Also, it will be easier to see if you specify ʻindent = 2` in the argument of JsonFormatter.

{
  "levelname": "INFO",
  "filename": "test_formatter.py",
  "message": "hello"
}

Of course, you can also output traceback. Since it is difficult to see if it is one line, it is arranged in an array.

{
  '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 !!'
  ]
}

Source code explanation

From here, I will explain the source code.

All source code

I will write all the source code for the time being.

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 This method is used when creating an instance. If it is not the expected type, use the default value or use it to assign None as it is.

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 It is a method to get the message of the argument of logger.info (). The return value of this getMessage is set in the JSON message field. If the instance type of record.msg is any of (list, tuple, dict), it is returned as it is, otherwise, the return value of the getMessage method of the record instance is returned. By doing so, when any of (list, tuple, dict) is passed, the log can be output as a JSON array / object.

def getMessage(self, record):
    if isinstance(record.msg, (list, tuple, dict)):
        return record.msg
    return record.getMessage()

_format_json This method dumps the argument record as JSON.

def _format_json(self, record):
    return json.dumps(record, ensure_ascii=False, indent=self._indent)

_format A method to convert record to a Python dictionary. The field specified by the user does not necessarily exist in the attribute of record, so it is try-except. The log of the return value is set to ʻOrderedDict` to guarantee the order.

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 This method is called from logging.Handler. I don't think you will create your own Formatter and use the format of the parent class (logging.Formatter), so be sure to overwrite this method. I'm formatting record.exc_text to output traceback as an array. After removing extra spaces etc., it is split with a line break to make an array. Finally, I dump the Python dict as 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)

Reference material

--logging --- logging function for Python

Recommended Posts

I tried to develop a Formatter that outputs Python logs in JSON
I tried to implement a pseudo pachislot in Python
I tried to implement a one-dimensional cellular automaton in Python
I tried "a program that removes duplicate statements in Python"
I tried "How to get a method decorated in Python"
I tried to make a stopwatch using tkinter in python
I tried to implement PLSA in Python
I tried to implement permutation in Python
I tried to implement ADALINE in Python
I tried to implement PPO in Python
[Python] A memo that I tried to get started with asyncio
I tried to implement a misunderstood prisoner's dilemma game in Python
A memo that I wrote a quicksort in Python
I want to create a window in Python
I tried playing a typing game in Python
How to create a JSON file in Python
[Memo] I tried a pivot table in Python
I tried to implement TOPIC MODEL in Python
I tried adding a Python3 module in C
A story that didn't work when I tried to log in with the Python requests module
I tried to implement selection sort in python
I tried to implement a card game of playing cards in Python
I want to create a priority queue that can be updated in Python (2.7)
[ES Lab] I tried to develop a WEB application with Python and Flask ②
[Python] I tried to make a Shiritori AI that enhances vocabulary through battles
I tried to implement what seems to be a Windows snipping tool in Python
I tried to graph the packages installed in Python
Parse a JSON string written to a file in Python
I want to easily implement a timeout in python
I tried to draw a route map with Python
I want to write in Python! (2) Let's write a test
I want to randomly sample a file in Python
I tried to implement Dragon Quest poker in Python
I want to work with a robot in python.
I tried to implement GA (genetic algorithm) in Python
I tried to automatically generate a password with Python3
I tried to summarize how to use pandas in python
[Python] I tried to get Json of squid ring 2
How to develop in Python
[Python + Bottle] I tried to release a web service that visualizes Twitter's positioned tweets.
I tried to create API list.csv in Python from swagger.yaml
A standard way to develop and distribute packages in Python
How to develop in a virtual environment of Python [Memo]
Create code that outputs "A and pretending B" in python
I created a class in Python and tried duck typing
I tried to implement the mail sending function in Python
I want to make input () a nice complement in python
I tried to implement blackjack of card game in Python
I tried to touch Python (installation)
I made a script in Python to convert a text file for JSON (for vscode user snippet)
[1 hour challenge] I tried to make a fortune-telling site that is too suitable with Python
I made a program to collect images in tweets that I liked on twitter with Python
I tried to automate "one heart even if separated" using a genetic algorithm in Python
I tried to make a generator that generates a C # container class from CSV with Python
I tried to create a Python script to get the value of a cell in Microsoft Excel
I also tried to imitate the function monad and State monad with a generator in Python
I wrote a doctest in "I tried to simulate the probability of a bingo game with Python"
I tried Line notification in Python
[5th] I tried to make a certain authenticator-like tool with python
I tried to create a server environment that runs on Windows 10
I want to use a wildcard that I want to shell with Python remove