New Upstream Release - python-pythonjsonlogger

Ready changes

Summary

Merged new upstream version: 2.0.7 (was: 2.0.4).

Diff

diff --git a/MANIFEST.in b/MANIFEST.in
index 90b3902..e35cf17 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,3 @@
 include LICENSE
-recursive-include tests *.py
\ No newline at end of file
+include README.md
+recursive-include tests *.py
diff --git a/PKG-INFO b/PKG-INFO
index c2405ed..80af15f 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,196 +1,199 @@
 Metadata-Version: 2.1
 Name: python-json-logger
-Version: 2.0.1
+Version: 2.0.7
 Summary: A python library adding a json log formatter
 Home-page: http://github.com/madzak/python-json-logger
 Author: Zakaria Zajac
 Author-email: zak@madzak.com
 License: BSD
-Description: [![Build Status](https://travis-ci.org/madzak/python-json-logger.svg?branch=master)](https://travis-ci.org/madzak/python-json-logger)
-        [![License](https://img.shields.io/pypi/l/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
-        [![Version](https://img.shields.io/pypi/v/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
-        
-        Overview
-        =======
-        This library is provided to allow standard python logging to output log data as json objects. With JSON we can make our logs more readable by machines and we can stop writing custom parsers for syslog type records.
-        
-        News
-        =======
-        Hi, I see this package is quiet alive and I am sorry for ignoring it so long. I will be stepping up my maintenance of this package so please allow me a week to get things back in order (and most likely a new minor version) and i'll post and update here once I am caught up.
-        
-        Installing
-        ==========
-        Pip:
-        
-            pip install python-json-logger
-        
-        Pypi:
-        
-           https://pypi.python.org/pypi/python-json-logger
-        
-        Manual:
-        
-            python setup.py install
-        
-        Usage
-        =====
-        
-        ## Integrating with Python's logging framework
-        
-        Json outputs are provided by the JsonFormatter logging formatter. You can add the custom formatter like below:
-        
-        **Please note: version 0.1.0 has changed the import structure, please update to the following example for proper importing**
-        
-        ```python
-            import logging
-            from pythonjsonlogger import jsonlogger
-        
-            logger = logging.getLogger()
-        
-            logHandler = logging.StreamHandler()
-            formatter = jsonlogger.JsonFormatter()
-            logHandler.setFormatter(formatter)
-            logger.addHandler(logHandler)
-        ```
-        
-        ## Customizing fields
-        
-        The fmt parser can also be overidden if you want to have required fields that differ from the default of just `message`.
-        
-        These two invocations are equivalent:
-        
-        ```python
-        class CustomJsonFormatter(jsonlogger.JsonFormatter):
-            def parse(self):
-                return self._fmt.split(';')
-        
-        formatter = CustomJsonFormatter('one;two')
-        
-        # is equivalent to:
-        
-        formatter = jsonlogger.JsonFormatter('%(one)s %(two)s')
-        ```
-        
-        You can also add extra fields to your json output by specifying a dict in place of message, as well as by specifying an `extra={}` argument.
-        
-        Contents of these dictionaries will be added at the root level of the entry and may override basic fields.
-        
-        You can also use the `add_fields` method to add to or generally normalize the set of default set of fields, it is called for every log event. For example, to unify default fields with those provided by [structlog](http://www.structlog.org/) you could do something like this:
-        
-        ```python
-        class CustomJsonFormatter(jsonlogger.JsonFormatter):
-            def add_fields(self, log_record, record, message_dict):
-                super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
-                if not log_record.get('timestamp'):
-                    # this doesn't use record.created, so it is slightly off
-                    now = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
-                    log_record['timestamp'] = now
-                if log_record.get('level'):
-                    log_record['level'] = log_record['level'].upper()
-                else:
-                    log_record['level'] = record.levelname
-        
-        formatter = CustomJsonFormatter('%(timestamp)s %(level)s %(name)s %(message)s')
-        ```
-        
-        Items added to the log record will be included in *every* log message, no matter what the format requires.
-        
-        ## Adding custom object serialization
-        
-        For custom handling of object serialization you can specify default json object translator or provide a custom encoder
-        
-        ```python
-        def json_translate(obj):
-            if isinstance(obj, MyClass):
-                return {"special": obj.special}
-        
-        formatter = jsonlogger.JsonFormatter(json_default=json_translate,
-                                             json_encoder=json.JSONEncoder)
-        logHandler.setFormatter(formatter)
-        
-        logger.info({"special": "value", "run": 12})
-        logger.info("classic message", extra={"special": "value", "run": 12})
-        ```
-        
-        ## Using a Config File
-        
-        To use the module with a config file using the [`fileConfig` function](https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig), use the class `pythonjsonlogger.jsonlogger.JsonFormatter`. Here is a sample config file.
-        
-        ```ini
-        [loggers]
-        keys = root,custom
-        
-        [logger_root]
-        handlers =
-        
-        [logger_custom]
-        level = INFO
-        handlers = custom
-        qualname = custom
-        
-        [handlers]
-        keys = custom
-        
-        [handler_custom]
-        class = StreamHandler
-        level = INFO
-        formatter = json
-        args = (sys.stdout,)
-        
-        [formatters]
-        keys = json
-        
-        [formatter_json]
-        format = %(message)s
-        class = pythonjsonlogger.jsonlogger.JsonFormatter
-        ```
-        
-        Example Output
-        ==============
-        
-        Sample JSON with a full formatter (basically the log message from the unit test). Every log message will appear on 1 line like a typical logger.
-        
-        ```json
-        {
-            "threadName": "MainThread",
-            "name": "root",
-            "thread": 140735202359648,
-            "created": 1336281068.506248,
-            "process": 41937,
-            "processName": "MainProcess",
-            "relativeCreated": 9.100914001464844,
-            "module": "tests",
-            "funcName": "testFormatKeys",
-            "levelno": 20,
-            "msecs": 506.24799728393555,
-            "pathname": "tests/tests.py",
-            "lineno": 60,
-            "asctime": ["12-05-05 22:11:08,506248"],
-            "message": "testing logging format",
-            "filename": "tests.py",
-            "levelname": "INFO",
-            "special": "value",
-            "run": 12
-        }
-        ```
-        
-        External Examples
-        =================
-        
-        - [Wesley Tanaka - Structured log files in Python using python-json-logger](https://wtanaka.com/node/8201)
-        
-Platform: UNKNOWN
-Classifier: Development Status :: 3 - Alpha
+Classifier: Development Status :: 6 - Mature
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: BSD License
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Topic :: System :: Logging
-Requires-Python: >=3.4
+Requires-Python: >=3.6
 Description-Content-Type: text/markdown
+License-File: LICENSE
+
+![Build Status](https://github.com/madzak/python-json-logger/actions/workflows/build.yml/badge.svg)
+[![License](https://img.shields.io/pypi/l/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
+[![Version](https://img.shields.io/pypi/v/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
+
+Overview
+=======
+This library is provided to allow standard python logging to output log data as json objects. With JSON we can make our logs more readable by machines and we can stop writing custom parsers for syslog type records.
+
+News
+=======
+Hi, I see this package is quiet alive and I am sorry for ignoring it so long. I will be stepping up my maintenance of this package so please allow me a week to get things back in order (and most likely a new minor version) and I'll post and update here once I am caught up.
+
+Installing
+==========
+Pip:
+
+    pip install python-json-logger
+
+Pypi:
+
+   https://pypi.python.org/pypi/python-json-logger
+
+Manual:
+
+    python setup.py install
+
+Usage
+=====
+
+## Integrating with Python's logging framework
+
+Json outputs are provided by the JsonFormatter logging formatter. You can add the custom formatter like below:
+
+**Please note: version 0.1.0 has changed the import structure, please update to the following example for proper importing**
+
+```python
+    import logging
+    from pythonjsonlogger import jsonlogger
+
+    logger = logging.getLogger()
+
+    logHandler = logging.StreamHandler()
+    formatter = jsonlogger.JsonFormatter()
+    logHandler.setFormatter(formatter)
+    logger.addHandler(logHandler)
+```
+
+## Customizing fields
+
+The fmt parser can also be overidden if you want to have required fields that differ from the default of just `message`.
+
+These two invocations are equivalent:
+
+```python
+class CustomJsonFormatter(jsonlogger.JsonFormatter):
+    def parse(self):
+        return self._fmt.split(';')
+
+formatter = CustomJsonFormatter('one;two')
+
+# is equivalent to:
+
+formatter = jsonlogger.JsonFormatter('%(one)s %(two)s')
+```
+
+You can also add extra fields to your json output by specifying a dict in place of message, as well as by specifying an `extra={}` argument.
+
+Contents of these dictionaries will be added at the root level of the entry and may override basic fields.
+
+You can also use the `add_fields` method to add to or generally normalize the set of default set of fields, it is called for every log event. For example, to unify default fields with those provided by [structlog](http://www.structlog.org/) you could do something like this:
+
+```python
+class CustomJsonFormatter(jsonlogger.JsonFormatter):
+    def add_fields(self, log_record, record, message_dict):
+        super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
+        if not log_record.get('timestamp'):
+            # this doesn't use record.created, so it is slightly off
+            now = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
+            log_record['timestamp'] = now
+        if log_record.get('level'):
+            log_record['level'] = log_record['level'].upper()
+        else:
+            log_record['level'] = record.levelname
+
+formatter = CustomJsonFormatter('%(timestamp)s %(level)s %(name)s %(message)s')
+```
+
+Items added to the log record will be included in *every* log message, no matter what the format requires.
+
+## Adding custom object serialization
+
+For custom handling of object serialization you can specify default json object translator or provide a custom encoder
+
+```python
+def json_translate(obj):
+    if isinstance(obj, MyClass):
+        return {"special": obj.special}
+
+formatter = jsonlogger.JsonFormatter(json_default=json_translate,
+                                     json_encoder=json.JSONEncoder)
+logHandler.setFormatter(formatter)
+
+logger.info({"special": "value", "run": 12})
+logger.info("classic message", extra={"special": "value", "run": 12})
+```
+
+## Using a Config File
+
+To use the module with a config file using the [`fileConfig` function](https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig), use the class `pythonjsonlogger.jsonlogger.JsonFormatter`. Here is a sample config file.
+
+```ini
+[loggers]
+keys = root,custom
+
+[logger_root]
+handlers =
+
+[logger_custom]
+level = INFO
+handlers = custom
+qualname = custom
+
+[handlers]
+keys = custom
+
+[handler_custom]
+class = StreamHandler
+level = INFO
+formatter = json
+args = (sys.stdout,)
+
+[formatters]
+keys = json
+
+[formatter_json]
+format = %(message)s
+class = pythonjsonlogger.jsonlogger.JsonFormatter
+```
+
+Example Output
+==============
+
+Sample JSON with a full formatter (basically the log message from the unit test). Every log message will appear on 1 line like a typical logger.
+
+```json
+{
+    "threadName": "MainThread",
+    "name": "root",
+    "thread": 140735202359648,
+    "created": 1336281068.506248,
+    "process": 41937,
+    "processName": "MainProcess",
+    "relativeCreated": 9.100914001464844,
+    "module": "tests",
+    "funcName": "testFormatKeys",
+    "levelno": 20,
+    "msecs": 506.24799728393555,
+    "pathname": "tests/tests.py",
+    "lineno": 60,
+    "asctime": ["12-05-05 22:11:08,506248"],
+    "message": "testing logging format",
+    "filename": "tests.py",
+    "levelname": "INFO",
+    "special": "value",
+    "run": 12
+}
+```
+
+External Examples
+=================
+
+- [Wesley Tanaka - Structured log files in Python using python-json-logger](http://web.archive.org/web/20201130054012/https://wtanaka.com/node/8201)
+
+- [Archive](https://web.archive.org/web/20201130054012/https://wtanaka.com/node/8201)
diff --git a/README.md b/README.md
index 21dddbf..2efa41c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[![Build Status](https://travis-ci.org/madzak/python-json-logger.svg?branch=master)](https://travis-ci.org/madzak/python-json-logger)
+![Build Status](https://github.com/madzak/python-json-logger/actions/workflows/build.yml/badge.svg)
 [![License](https://img.shields.io/pypi/l/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
 [![Version](https://img.shields.io/pypi/v/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
 
@@ -8,7 +8,7 @@ This library is provided to allow standard python logging to output log data as
 
 News
 =======
-Hi, I see this package is quiet alive and I am sorry for ignoring it so long. I will be stepping up my maintenance of this package so please allow me a week to get things back in order (and most likely a new minor version) and i'll post and update here once I am caught up.
+Hi, I see this package is quiet alive and I am sorry for ignoring it so long. I will be stepping up my maintenance of this package so please allow me a week to get things back in order (and most likely a new minor version) and I'll post and update here once I am caught up.
 
 Installing
 ==========
@@ -169,4 +169,6 @@ Sample JSON with a full formatter (basically the log message from the unit test)
 External Examples
 =================
 
-- [Wesley Tanaka - Structured log files in Python using python-json-logger](https://wtanaka.com/node/8201)
+- [Wesley Tanaka - Structured log files in Python using python-json-logger](http://web.archive.org/web/20201130054012/https://wtanaka.com/node/8201)
+
+- [Archive](https://web.archive.org/web/20201130054012/https://wtanaka.com/node/8201)
diff --git a/debian/changelog b/debian/changelog
index e42da79..978fd1a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,10 @@
-python-pythonjsonlogger (2.0.1-4) UNRELEASED; urgency=medium
+python-pythonjsonlogger (2.0.7-1) UNRELEASED; urgency=medium
 
   * Update standards version to 4.6.2, no changes needed.
+  * New upstream release.
+  * New upstream release.
 
- -- Debian Janitor <janitor@jelmer.uk>  Sat, 07 Jan 2023 17:48:27 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 01 May 2023 23:54:57 -0000
 
 python-pythonjsonlogger (2.0.1-3) unstable; urgency=medium
 
diff --git a/setup.cfg b/setup.cfg
index 52ab2e7..8e78f91 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,32 @@
-[bdist_wheel]
-python-tag = py34
+[mypy]
+mypy_path = src
+namespace_packages = true
+disallow_any_unimported = true
+disallow_any_expr = false
+disallow_any_decorated = true
+disallow_any_explicit = false
+disallow_any_generics = false
+disallow_subclassing_any = true
+disallow_untyped_calls = false
+disallow_untyped_defs = false
+disallow_incomplete_defs = true
+check_untyped_defs = true
+disallow_untyped_decorators = true
+no_implicit_optional = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+warn_no_return = true
+warn_return_any = true
+warn_unreachable = true
+implicit_reexport = true
+strict_equality = true
+show_error_context = true
+show_column_numbers = true
+show_error_codes = true
+pretty = true
+show_absolute_path = true
+warn_unused_configs = true
+verbosity = 0
 
 [egg_info]
 tag_build = 
diff --git a/setup.py b/setup.py
index 5b8850a..74320b3 100644
--- a/setup.py
+++ b/setup.py
@@ -1,38 +1,41 @@
+from os import path
 from setuptools import setup, find_packages
 
 # read the contents of your README file
-from os import path
 this_directory = path.abspath(path.dirname(__file__))
 with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
     long_description = f.read()
 
 setup(
     name="python-json-logger",
-    version="2.0.1",
+    version="2.0.7",
     url="http://github.com/madzak/python-json-logger",
     license="BSD",
+    include_package_data=True,
     description="A python library adding a json log formatter",
     long_description=long_description,
     long_description_content_type='text/markdown',
     author="Zakaria Zajac",
     author_email="zak@madzak.com",
     package_dir={'': 'src'},
+    package_data={"pythonjsonlogger": ["py.typed"]},
     packages=find_packages("src", exclude="tests"),
     # https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
-    python_requires='>=3.4',
+    python_requires=">=3.6",
     test_suite="tests.tests",
     classifiers=[
-        'Development Status :: 3 - Alpha',
+        'Development Status :: 6 - Mature',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: BSD License',
         'Operating System :: OS Independent',
         'Programming Language :: Python',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.4',
-        'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
+        'Programming Language :: Python :: 3.11',
         'Topic :: System :: Logging',
     ]
 )
diff --git a/src/python_json_logger.egg-info/PKG-INFO b/src/python_json_logger.egg-info/PKG-INFO
index c2405ed..80af15f 100644
--- a/src/python_json_logger.egg-info/PKG-INFO
+++ b/src/python_json_logger.egg-info/PKG-INFO
@@ -1,196 +1,199 @@
 Metadata-Version: 2.1
 Name: python-json-logger
-Version: 2.0.1
+Version: 2.0.7
 Summary: A python library adding a json log formatter
 Home-page: http://github.com/madzak/python-json-logger
 Author: Zakaria Zajac
 Author-email: zak@madzak.com
 License: BSD
-Description: [![Build Status](https://travis-ci.org/madzak/python-json-logger.svg?branch=master)](https://travis-ci.org/madzak/python-json-logger)
-        [![License](https://img.shields.io/pypi/l/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
-        [![Version](https://img.shields.io/pypi/v/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
-        
-        Overview
-        =======
-        This library is provided to allow standard python logging to output log data as json objects. With JSON we can make our logs more readable by machines and we can stop writing custom parsers for syslog type records.
-        
-        News
-        =======
-        Hi, I see this package is quiet alive and I am sorry for ignoring it so long. I will be stepping up my maintenance of this package so please allow me a week to get things back in order (and most likely a new minor version) and i'll post and update here once I am caught up.
-        
-        Installing
-        ==========
-        Pip:
-        
-            pip install python-json-logger
-        
-        Pypi:
-        
-           https://pypi.python.org/pypi/python-json-logger
-        
-        Manual:
-        
-            python setup.py install
-        
-        Usage
-        =====
-        
-        ## Integrating with Python's logging framework
-        
-        Json outputs are provided by the JsonFormatter logging formatter. You can add the custom formatter like below:
-        
-        **Please note: version 0.1.0 has changed the import structure, please update to the following example for proper importing**
-        
-        ```python
-            import logging
-            from pythonjsonlogger import jsonlogger
-        
-            logger = logging.getLogger()
-        
-            logHandler = logging.StreamHandler()
-            formatter = jsonlogger.JsonFormatter()
-            logHandler.setFormatter(formatter)
-            logger.addHandler(logHandler)
-        ```
-        
-        ## Customizing fields
-        
-        The fmt parser can also be overidden if you want to have required fields that differ from the default of just `message`.
-        
-        These two invocations are equivalent:
-        
-        ```python
-        class CustomJsonFormatter(jsonlogger.JsonFormatter):
-            def parse(self):
-                return self._fmt.split(';')
-        
-        formatter = CustomJsonFormatter('one;two')
-        
-        # is equivalent to:
-        
-        formatter = jsonlogger.JsonFormatter('%(one)s %(two)s')
-        ```
-        
-        You can also add extra fields to your json output by specifying a dict in place of message, as well as by specifying an `extra={}` argument.
-        
-        Contents of these dictionaries will be added at the root level of the entry and may override basic fields.
-        
-        You can also use the `add_fields` method to add to or generally normalize the set of default set of fields, it is called for every log event. For example, to unify default fields with those provided by [structlog](http://www.structlog.org/) you could do something like this:
-        
-        ```python
-        class CustomJsonFormatter(jsonlogger.JsonFormatter):
-            def add_fields(self, log_record, record, message_dict):
-                super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
-                if not log_record.get('timestamp'):
-                    # this doesn't use record.created, so it is slightly off
-                    now = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
-                    log_record['timestamp'] = now
-                if log_record.get('level'):
-                    log_record['level'] = log_record['level'].upper()
-                else:
-                    log_record['level'] = record.levelname
-        
-        formatter = CustomJsonFormatter('%(timestamp)s %(level)s %(name)s %(message)s')
-        ```
-        
-        Items added to the log record will be included in *every* log message, no matter what the format requires.
-        
-        ## Adding custom object serialization
-        
-        For custom handling of object serialization you can specify default json object translator or provide a custom encoder
-        
-        ```python
-        def json_translate(obj):
-            if isinstance(obj, MyClass):
-                return {"special": obj.special}
-        
-        formatter = jsonlogger.JsonFormatter(json_default=json_translate,
-                                             json_encoder=json.JSONEncoder)
-        logHandler.setFormatter(formatter)
-        
-        logger.info({"special": "value", "run": 12})
-        logger.info("classic message", extra={"special": "value", "run": 12})
-        ```
-        
-        ## Using a Config File
-        
-        To use the module with a config file using the [`fileConfig` function](https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig), use the class `pythonjsonlogger.jsonlogger.JsonFormatter`. Here is a sample config file.
-        
-        ```ini
-        [loggers]
-        keys = root,custom
-        
-        [logger_root]
-        handlers =
-        
-        [logger_custom]
-        level = INFO
-        handlers = custom
-        qualname = custom
-        
-        [handlers]
-        keys = custom
-        
-        [handler_custom]
-        class = StreamHandler
-        level = INFO
-        formatter = json
-        args = (sys.stdout,)
-        
-        [formatters]
-        keys = json
-        
-        [formatter_json]
-        format = %(message)s
-        class = pythonjsonlogger.jsonlogger.JsonFormatter
-        ```
-        
-        Example Output
-        ==============
-        
-        Sample JSON with a full formatter (basically the log message from the unit test). Every log message will appear on 1 line like a typical logger.
-        
-        ```json
-        {
-            "threadName": "MainThread",
-            "name": "root",
-            "thread": 140735202359648,
-            "created": 1336281068.506248,
-            "process": 41937,
-            "processName": "MainProcess",
-            "relativeCreated": 9.100914001464844,
-            "module": "tests",
-            "funcName": "testFormatKeys",
-            "levelno": 20,
-            "msecs": 506.24799728393555,
-            "pathname": "tests/tests.py",
-            "lineno": 60,
-            "asctime": ["12-05-05 22:11:08,506248"],
-            "message": "testing logging format",
-            "filename": "tests.py",
-            "levelname": "INFO",
-            "special": "value",
-            "run": 12
-        }
-        ```
-        
-        External Examples
-        =================
-        
-        - [Wesley Tanaka - Structured log files in Python using python-json-logger](https://wtanaka.com/node/8201)
-        
-Platform: UNKNOWN
-Classifier: Development Status :: 3 - Alpha
+Classifier: Development Status :: 6 - Mature
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: BSD License
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Topic :: System :: Logging
-Requires-Python: >=3.4
+Requires-Python: >=3.6
 Description-Content-Type: text/markdown
+License-File: LICENSE
+
+![Build Status](https://github.com/madzak/python-json-logger/actions/workflows/build.yml/badge.svg)
+[![License](https://img.shields.io/pypi/l/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
+[![Version](https://img.shields.io/pypi/v/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
+
+Overview
+=======
+This library is provided to allow standard python logging to output log data as json objects. With JSON we can make our logs more readable by machines and we can stop writing custom parsers for syslog type records.
+
+News
+=======
+Hi, I see this package is quiet alive and I am sorry for ignoring it so long. I will be stepping up my maintenance of this package so please allow me a week to get things back in order (and most likely a new minor version) and I'll post and update here once I am caught up.
+
+Installing
+==========
+Pip:
+
+    pip install python-json-logger
+
+Pypi:
+
+   https://pypi.python.org/pypi/python-json-logger
+
+Manual:
+
+    python setup.py install
+
+Usage
+=====
+
+## Integrating with Python's logging framework
+
+Json outputs are provided by the JsonFormatter logging formatter. You can add the custom formatter like below:
+
+**Please note: version 0.1.0 has changed the import structure, please update to the following example for proper importing**
+
+```python
+    import logging
+    from pythonjsonlogger import jsonlogger
+
+    logger = logging.getLogger()
+
+    logHandler = logging.StreamHandler()
+    formatter = jsonlogger.JsonFormatter()
+    logHandler.setFormatter(formatter)
+    logger.addHandler(logHandler)
+```
+
+## Customizing fields
+
+The fmt parser can also be overidden if you want to have required fields that differ from the default of just `message`.
+
+These two invocations are equivalent:
+
+```python
+class CustomJsonFormatter(jsonlogger.JsonFormatter):
+    def parse(self):
+        return self._fmt.split(';')
+
+formatter = CustomJsonFormatter('one;two')
+
+# is equivalent to:
+
+formatter = jsonlogger.JsonFormatter('%(one)s %(two)s')
+```
+
+You can also add extra fields to your json output by specifying a dict in place of message, as well as by specifying an `extra={}` argument.
+
+Contents of these dictionaries will be added at the root level of the entry and may override basic fields.
+
+You can also use the `add_fields` method to add to or generally normalize the set of default set of fields, it is called for every log event. For example, to unify default fields with those provided by [structlog](http://www.structlog.org/) you could do something like this:
+
+```python
+class CustomJsonFormatter(jsonlogger.JsonFormatter):
+    def add_fields(self, log_record, record, message_dict):
+        super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
+        if not log_record.get('timestamp'):
+            # this doesn't use record.created, so it is slightly off
+            now = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
+            log_record['timestamp'] = now
+        if log_record.get('level'):
+            log_record['level'] = log_record['level'].upper()
+        else:
+            log_record['level'] = record.levelname
+
+formatter = CustomJsonFormatter('%(timestamp)s %(level)s %(name)s %(message)s')
+```
+
+Items added to the log record will be included in *every* log message, no matter what the format requires.
+
+## Adding custom object serialization
+
+For custom handling of object serialization you can specify default json object translator or provide a custom encoder
+
+```python
+def json_translate(obj):
+    if isinstance(obj, MyClass):
+        return {"special": obj.special}
+
+formatter = jsonlogger.JsonFormatter(json_default=json_translate,
+                                     json_encoder=json.JSONEncoder)
+logHandler.setFormatter(formatter)
+
+logger.info({"special": "value", "run": 12})
+logger.info("classic message", extra={"special": "value", "run": 12})
+```
+
+## Using a Config File
+
+To use the module with a config file using the [`fileConfig` function](https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig), use the class `pythonjsonlogger.jsonlogger.JsonFormatter`. Here is a sample config file.
+
+```ini
+[loggers]
+keys = root,custom
+
+[logger_root]
+handlers =
+
+[logger_custom]
+level = INFO
+handlers = custom
+qualname = custom
+
+[handlers]
+keys = custom
+
+[handler_custom]
+class = StreamHandler
+level = INFO
+formatter = json
+args = (sys.stdout,)
+
+[formatters]
+keys = json
+
+[formatter_json]
+format = %(message)s
+class = pythonjsonlogger.jsonlogger.JsonFormatter
+```
+
+Example Output
+==============
+
+Sample JSON with a full formatter (basically the log message from the unit test). Every log message will appear on 1 line like a typical logger.
+
+```json
+{
+    "threadName": "MainThread",
+    "name": "root",
+    "thread": 140735202359648,
+    "created": 1336281068.506248,
+    "process": 41937,
+    "processName": "MainProcess",
+    "relativeCreated": 9.100914001464844,
+    "module": "tests",
+    "funcName": "testFormatKeys",
+    "levelno": 20,
+    "msecs": 506.24799728393555,
+    "pathname": "tests/tests.py",
+    "lineno": 60,
+    "asctime": ["12-05-05 22:11:08,506248"],
+    "message": "testing logging format",
+    "filename": "tests.py",
+    "levelname": "INFO",
+    "special": "value",
+    "run": 12
+}
+```
+
+External Examples
+=================
+
+- [Wesley Tanaka - Structured log files in Python using python-json-logger](http://web.archive.org/web/20201130054012/https://wtanaka.com/node/8201)
+
+- [Archive](https://web.archive.org/web/20201130054012/https://wtanaka.com/node/8201)
diff --git a/src/python_json_logger.egg-info/SOURCES.txt b/src/python_json_logger.egg-info/SOURCES.txt
index d99c776..0db51aa 100644
--- a/src/python_json_logger.egg-info/SOURCES.txt
+++ b/src/python_json_logger.egg-info/SOURCES.txt
@@ -9,5 +9,6 @@ src/python_json_logger.egg-info/dependency_links.txt
 src/python_json_logger.egg-info/top_level.txt
 src/pythonjsonlogger/__init__.py
 src/pythonjsonlogger/jsonlogger.py
+src/pythonjsonlogger/py.typed
 tests/__init__.py
-tests/tests.py
\ No newline at end of file
+tests/test_jsonlogger.py
\ No newline at end of file
diff --git a/src/pythonjsonlogger/jsonlogger.py b/src/pythonjsonlogger/jsonlogger.py
index 9ba1605..e250c7e 100644
--- a/src/pythonjsonlogger/jsonlogger.py
+++ b/src/pythonjsonlogger/jsonlogger.py
@@ -9,33 +9,45 @@ from datetime import date, datetime, time, timezone
 import traceback
 import importlib
 
+from typing import Any, Dict, Optional, Union, List, Tuple
+
 from inspect import istraceback
 
 from collections import OrderedDict
 
 # skip natural LogRecord attributes
 # http://docs.python.org/library/logging.html#logrecord-attributes
-RESERVED_ATTRS = (
+RESERVED_ATTRS: Tuple[str, ...] = (
     'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename',
     'funcName', 'levelname', 'levelno', 'lineno', 'module',
     'msecs', 'message', 'msg', 'name', 'pathname', 'process',
     'processName', 'relativeCreated', 'stack_info', 'thread', 'threadName')
 
 
-def merge_record_extra(record, target, reserved):
+
+def merge_record_extra(
+    record: logging.LogRecord,
+    target: Dict,
+    reserved: Union[Dict, List],
+    rename_fields: Optional[Dict[str,str]] = None,
+) -> Dict:
     """
     Merges extra attributes from LogRecord object into target dictionary
 
     :param record: logging.LogRecord
     :param target: dict to update
     :param reserved: dict or list with reserved keys to skip
+    :param rename_fields: an optional dict, used to rename field names in the output.
+            Rename levelname to log.level: {'levelname': 'log.level'}
     """
+    if rename_fields is None:
+        rename_fields = {}
     for key, value in record.__dict__.items():
         # this allows to have numeric keys
         if (key not in reserved
             and not (hasattr(key, "startswith")
                      and key.startswith('_'))):
-            target[key] = value
+            target[rename_fields.get(key,key)] = value
     return target
 
 
@@ -80,7 +92,7 @@ class JsonFormatter(logging.Formatter):
     def __init__(self, *args, **kwargs):
         """
         :param json_default: a function for encoding non-standard objects
-            as outlined in http://docs.python.org/2/library/json.html
+            as outlined in https://docs.python.org/3/library/json.html
         :param json_encoder: optional custom encoder
         :param json_serializer: a :meth:`json.dumps`-compatible callable
             that will be used to serialize the log record.
@@ -90,6 +102,7 @@ class JsonFormatter(logging.Formatter):
             the formatted string
         :param rename_fields: an optional dict, used to rename field names in the output.
             Rename message to @message: {'message': '@message'}
+        :param static_fields: an optional dict, used to add fields with static values to all logs
         :param json_indent: indent parameter for json.dumps
         :param json_ensure_ascii: ensure_ascii parameter for json.dumps
         :param reserved_attrs: an optional list of fields that will be skipped when
@@ -107,6 +120,7 @@ class JsonFormatter(logging.Formatter):
         self.json_ensure_ascii = kwargs.pop("json_ensure_ascii", True)
         self.prefix = kwargs.pop("prefix", "")
         self.rename_fields = kwargs.pop("rename_fields", {})
+        self.static_fields = kwargs.pop("static_fields", {})
         reserved_attrs = kwargs.pop("reserved_attrs", RESERVED_ATTRS)
         self.reserved_attrs = dict(zip(reserved_attrs, reserved_attrs))
         self.timestamp = kwargs.pop("timestamp", False)
@@ -136,32 +150,51 @@ class JsonFormatter(logging.Formatter):
         module = importlib.import_module(path)
         return getattr(module, function)
 
-    def parse(self):
+    def parse(self) -> List[str]:
         """
         Parses format string looking for substitutions
 
         This method is responsible for returning a list of fields (as strings)
         to include in all log messages.
         """
-        standard_formatters = re.compile(r'\((.+?)\)', re.IGNORECASE)
-        return standard_formatters.findall(self._fmt)
+        if isinstance(self._style, logging.StringTemplateStyle):
+            formatter_style_pattern = re.compile(r'\$\{(.+?)\}', re.IGNORECASE)
+        elif isinstance(self._style, logging.StrFormatStyle):
+            formatter_style_pattern = re.compile(r'\{(.+?)\}', re.IGNORECASE)
+        # PercentStyle is parent class of StringTemplateStyle and StrFormatStyle so
+        # it needs to be checked last.
+        elif isinstance(self._style, logging.PercentStyle):
+            formatter_style_pattern = re.compile(r'%\((.+?)\)', re.IGNORECASE)
+        else:
+            raise ValueError('Invalid format: %s' % self._fmt)
 
-    def add_fields(self, log_record, record, message_dict):
+        if self._fmt:
+            return formatter_style_pattern.findall(self._fmt)
+        else:
+            return []
+
+    def add_fields(self, log_record: Dict[str, Any], record: logging.LogRecord, message_dict: Dict[str, Any]) -> None:
         """
         Override this method to implement custom logic for adding fields.
         """
         for field in self._required_fields:
-            if field in self.rename_fields:
-                log_record[self.rename_fields[field]] = record.__dict__.get(field)
-            else:
-                log_record[field] = record.__dict__.get(field)
+            log_record[field] = record.__dict__.get(field)
+
+        log_record.update(self.static_fields)
         log_record.update(message_dict)
-        merge_record_extra(record, log_record, reserved=self._skip_fields)
+        merge_record_extra(record, log_record, reserved=self._skip_fields, rename_fields=self.rename_fields)
 
         if self.timestamp:
             key = self.timestamp if type(self.timestamp) == str else 'timestamp'
             log_record[key] = datetime.fromtimestamp(record.created, tz=timezone.utc)
 
+        self._perform_rename_log_fields(log_record)
+
+    def _perform_rename_log_fields(self, log_record):
+        for old_field_name, new_field_name in self.rename_fields.items():
+            log_record[new_field_name] = log_record[old_field_name]
+            del log_record[old_field_name]
+
     def process_log_record(self, log_record):
         """
         Override this method to implement custom logic
@@ -177,16 +210,18 @@ class JsonFormatter(logging.Formatter):
                                     indent=self.json_indent,
                                     ensure_ascii=self.json_ensure_ascii)
 
-    def serialize_log_record(self, log_record):
+    def serialize_log_record(self, log_record: Dict[str, Any]) -> str:
         """Returns the final representation of the log record."""
         return "%s%s" % (self.prefix, self.jsonify_log_record(log_record))
 
-    def format(self, record):
+    def format(self, record: logging.LogRecord) -> str:
         """Formats a log record and serializes to json"""
-        message_dict = {}
+        message_dict: Dict[str, Any] = {}
+        # FIXME: logging.LogRecord.msg and logging.LogRecord.message in typeshed
+        #        are always type of str. We shouldn't need to override that.
         if isinstance(record.msg, dict):
             message_dict = record.msg
-            record.message = None
+            record.message = ""
         else:
             record.message = record.getMessage()
         # only format time if needed
@@ -201,18 +236,10 @@ class JsonFormatter(logging.Formatter):
             message_dict['exc_info'] = record.exc_text
         # Display formatted record of stack frames
         # default format is a string returned from :func:`traceback.print_stack`
-        try:
-            if record.stack_info and not message_dict.get('stack_info'):
-                message_dict['stack_info'] = self.formatStack(record.stack_info)
-        except AttributeError:
-            # Python2.7 doesn't have stack_info.
-            pass
-
-        try:
-            log_record = OrderedDict()
-        except NameError:
-            log_record = {}
+        if record.stack_info and not message_dict.get('stack_info'):
+            message_dict['stack_info'] = self.formatStack(record.stack_info)
 
+        log_record: Dict[str, Any] = OrderedDict()
         self.add_fields(log_record, record, message_dict)
         log_record = self.process_log_record(log_record)
 
diff --git a/src/pythonjsonlogger/py.typed b/src/pythonjsonlogger/py.typed
new file mode 100644
index 0000000..89afa56
--- /dev/null
+++ b/src/pythonjsonlogger/py.typed
@@ -0,0 +1 @@
+# PEP-561 marker. https://mypy.readthedocs.io/en/latest/installed_packages.html
diff --git a/tests/test_jsonlogger.py b/tests/test_jsonlogger.py
new file mode 100644
index 0000000..af369d2
--- /dev/null
+++ b/tests/test_jsonlogger.py
@@ -0,0 +1,317 @@
+# -*- coding: utf-8 -*-
+import unittest
+import unittest.mock
+import logging
+import json
+import sys
+import traceback
+import random
+
+try:
+    import xmlrunner  # noqa
+except ImportError:
+    pass
+
+from io import StringIO
+
+sys.path.append('src/python-json-logger')
+from pythonjsonlogger import jsonlogger
+import datetime
+
+
+class TestJsonLogger(unittest.TestCase):
+    def setUp(self):
+        self.log = logging.getLogger("logging-test-{}".format(random.randint(1, 101)))
+        self.log.setLevel(logging.DEBUG)
+        self.buffer = StringIO()
+
+        self.log_handler = logging.StreamHandler(self.buffer)
+        self.log.addHandler(self.log_handler)
+
+    def test_default_format(self):
+        fr = jsonlogger.JsonFormatter()
+        self.log_handler.setFormatter(fr)
+
+        msg = "testing logging format"
+        self.log.info(msg)
+        log_json = json.loads(self.buffer.getvalue())
+
+        self.assertEqual(log_json["message"], msg)
+
+    def test_percentage_format(self):
+        fr = jsonlogger.JsonFormatter(
+            # All kind of different styles to check the regex
+            '[%(levelname)8s] %(message)s %(filename)s:%(lineno)d %(asctime)'
+        )
+        self.log_handler.setFormatter(fr)
+
+        msg = "testing logging format"
+        self.log.info(msg)
+        log_json = json.loads(self.buffer.getvalue())
+
+        self.assertEqual(log_json["message"], msg)
+        self.assertEqual(log_json.keys(), {'levelname', 'message', 'filename', 'lineno', 'asctime'})
+
+    def test_rename_base_field(self):
+        fr = jsonlogger.JsonFormatter(rename_fields={'message': '@message'})
+        self.log_handler.setFormatter(fr)
+
+        msg = "testing logging format"
+        self.log.info(msg)
+        log_json = json.loads(self.buffer.getvalue())
+
+        self.assertEqual(log_json["@message"], msg)
+
+    def test_rename_nonexistent_field(self):
+        fr = jsonlogger.JsonFormatter(rename_fields={'nonexistent_key': 'new_name'})
+        self.log_handler.setFormatter(fr)
+
+        stderr_watcher = StringIO()
+        sys.stderr = stderr_watcher
+        self.log.info("testing logging rename")
+
+        self.assertTrue("KeyError: 'nonexistent_key'" in stderr_watcher.getvalue())
+
+    def test_add_static_fields(self):
+        fr = jsonlogger.JsonFormatter(static_fields={'log_stream': 'kafka'})
+
+        self.log_handler.setFormatter(fr)
+
+        msg = "testing static fields"
+        self.log.info(msg)
+        log_json = json.loads(self.buffer.getvalue())
+
+        self.assertEqual(log_json["log_stream"], "kafka")
+        self.assertEqual(log_json["message"], msg)
+
+    def test_format_keys(self):
+        supported_keys = [
+            'asctime',
+            'created',
+            'filename',
+            'funcName',
+            'levelname',
+            'levelno',
+            'lineno',
+            'module',
+            'msecs',
+            'message',
+            'name',
+            'pathname',
+            'process',
+            'processName',
+            'relativeCreated',
+            'thread',
+            'threadName'
+        ]
+
+        log_format = lambda x: ['%({0:s})s'.format(i) for i in x]
+        custom_format = ' '.join(log_format(supported_keys))
+
+        fr = jsonlogger.JsonFormatter(custom_format)
+        self.log_handler.setFormatter(fr)
+
+        msg = "testing logging format"
+        self.log.info(msg)
+        log_msg = self.buffer.getvalue()
+        log_json = json.loads(log_msg)
+
+        for supported_key in supported_keys:
+            if supported_key in log_json:
+                self.assertTrue(True)
+
+    def test_unknown_format_key(self):
+        fr = jsonlogger.JsonFormatter('%(unknown_key)s %(message)s')
+
+        self.log_handler.setFormatter(fr)
+        msg = "testing unknown logging format"
+        try:
+            self.log.info(msg)
+        except Exception:
+            self.assertTrue(False, "Should succeed")
+
+    def test_log_adict(self):
+        fr = jsonlogger.JsonFormatter()
+        self.log_handler.setFormatter(fr)
+
+        msg = {"text": "testing logging", "num": 1, 5: "9",
+               "nested": {"more": "data"}}
+
+        self.log.info(msg)
+        log_json = json.loads(self.buffer.getvalue())
+        self.assertEqual(log_json.get("text"), msg["text"])
+        self.assertEqual(log_json.get("num"), msg["num"])
+        self.assertEqual(log_json.get("5"), msg[5])
+        self.assertEqual(log_json.get("nested"), msg["nested"])
+        self.assertEqual(log_json["message"], "")
+
+    def test_log_extra(self):
+        fr = jsonlogger.JsonFormatter()
+        self.log_handler.setFormatter(fr)
+
+        extra = {"text": "testing logging", "num": 1, 5: "9",
+                 "nested": {"more": "data"}}
+        self.log.info("hello", extra=extra)
+        log_json = json.loads(self.buffer.getvalue())
+        self.assertEqual(log_json.get("text"), extra["text"])
+        self.assertEqual(log_json.get("num"), extra["num"])
+        self.assertEqual(log_json.get("5"), extra[5])
+        self.assertEqual(log_json.get("nested"), extra["nested"])
+        self.assertEqual(log_json["message"], "hello")
+
+    def test_json_default_encoder(self):
+        fr = jsonlogger.JsonFormatter()
+        self.log_handler.setFormatter(fr)
+
+        msg = {"adate": datetime.datetime(1999, 12, 31, 23, 59),
+               "otherdate": datetime.date(1789, 7, 14),
+               "otherdatetime": datetime.datetime(1789, 7, 14, 23, 59),
+               "otherdatetimeagain": datetime.datetime(1900, 1, 1)}
+        self.log.info(msg)
+        log_json = json.loads(self.buffer.getvalue())
+        self.assertEqual(log_json.get("adate"), "1999-12-31T23:59:00")
+        self.assertEqual(log_json.get("otherdate"), "1789-07-14")
+        self.assertEqual(log_json.get("otherdatetime"), "1789-07-14T23:59:00")
+        self.assertEqual(log_json.get("otherdatetimeagain"),
+                         "1900-01-01T00:00:00")
+
+    @unittest.mock.patch('time.time', return_value=1500000000.0)
+    def test_json_default_encoder_with_timestamp(self, time_mock):
+        fr = jsonlogger.JsonFormatter(timestamp=True)
+        self.log_handler.setFormatter(fr)
+
+        self.log.info("Hello")
+
+        self.assertTrue(time_mock.called)
+        log_json = json.loads(self.buffer.getvalue())
+        self.assertEqual(log_json.get("timestamp"), "2017-07-14T02:40:00+00:00")
+
+    def test_json_custom_default(self):
+        def custom(o):
+            return "very custom"
+        fr = jsonlogger.JsonFormatter(json_default=custom)
+        self.log_handler.setFormatter(fr)
+
+        msg = {"adate": datetime.datetime(1999, 12, 31, 23, 59),
+               "normal": "value"}
+        self.log.info(msg)
+        log_json = json.loads(self.buffer.getvalue())
+        self.assertEqual(log_json.get("adate"), "very custom")
+        self.assertEqual(log_json.get("normal"), "value")
+
+    def test_json_custom_logic_adds_field(self):
+        class CustomJsonFormatter(jsonlogger.JsonFormatter):
+
+            def process_log_record(self, log_record):
+                log_record["custom"] = "value"
+                # Old Style "super" since Python 2.6's logging.Formatter is old
+                # style
+                return jsonlogger.JsonFormatter.process_log_record(self, log_record)
+
+        self.log_handler.setFormatter(CustomJsonFormatter())
+        self.log.info("message")
+        log_json = json.loads(self.buffer.getvalue())
+        self.assertEqual(log_json.get("custom"), "value")
+
+    def get_traceback_from_exception_followed_by_log_call(self) -> str:
+        try:
+            raise Exception('test')
+        except Exception:
+            self.log.exception("hello")
+            str_traceback = traceback.format_exc()
+            # Formatter removes trailing new line
+            if str_traceback.endswith('\n'):
+                str_traceback = str_traceback[:-1]
+
+        return str_traceback
+
+    def test_exc_info(self):
+        fr = jsonlogger.JsonFormatter()
+        self.log_handler.setFormatter(fr)
+        expected_value = self.get_traceback_from_exception_followed_by_log_call()
+
+        log_json = json.loads(self.buffer.getvalue())
+        self.assertEqual(log_json.get("exc_info"), expected_value)
+
+    def test_exc_info_renamed(self):
+        fr = jsonlogger.JsonFormatter("%(exc_info)s", rename_fields={"exc_info": "stack_trace"})
+        self.log_handler.setFormatter(fr)
+        expected_value = self.get_traceback_from_exception_followed_by_log_call()
+
+        log_json = json.loads(self.buffer.getvalue())
+        self.assertEqual(log_json.get("stack_trace"), expected_value)
+        self.assertEqual(log_json.get("exc_info"), None)
+
+    def test_ensure_ascii_true(self):
+        fr = jsonlogger.JsonFormatter()
+        self.log_handler.setFormatter(fr)
+        self.log.info('Привет')
+        msg = self.buffer.getvalue().split('"message": "', 1)[1].split('"', 1)[0]
+        self.assertEqual(msg, r"\u041f\u0440\u0438\u0432\u0435\u0442")
+
+    def test_ensure_ascii_false(self):
+        fr = jsonlogger.JsonFormatter(json_ensure_ascii=False)
+        self.log_handler.setFormatter(fr)
+        self.log.info('Привет')
+        msg = self.buffer.getvalue().split('"message": "', 1)[1].split('"', 1)[0]
+        self.assertEqual(msg, "Привет")
+
+    def test_custom_object_serialization(self):
+        def encode_complex(z):
+            if isinstance(z, complex):
+                return (z.real, z.imag)
+            else:
+                type_name = z.__class__.__name__
+                raise TypeError("Object of type '{}' is no JSON serializable".format(type_name))
+
+        formatter = jsonlogger.JsonFormatter(json_default=encode_complex,
+                                             json_encoder=json.JSONEncoder)
+        self.log_handler.setFormatter(formatter)
+
+        value = {
+            "special": complex(3, 8),
+        }
+
+        self.log.info(" message", extra=value)
+        msg = self.buffer.getvalue()
+        self.assertEqual(msg, "{\"message\": \" message\", \"special\": [3.0, 8.0]}\n")
+
+    def test_rename_reserved_attrs(self):
+        log_format = lambda x: ['%({0:s})s'.format(i) for i in x]
+        reserved_attrs_map = {
+            'exc_info': 'error.type',
+            'exc_text': 'error.message',
+            'funcName': 'log.origin.function',
+            'levelname': 'log.level',
+            'module': 'log.origin.file.name',
+            'processName': 'process.name',
+            'threadName': 'process.thread.name',
+            'msg': 'log.message'
+        }
+
+        custom_format = ' '.join(log_format(reserved_attrs_map.keys()))
+        reserved_attrs = [_ for _ in jsonlogger.RESERVED_ATTRS if _ not in list(reserved_attrs_map.keys())]
+        formatter = jsonlogger.JsonFormatter(custom_format, reserved_attrs=reserved_attrs, rename_fields=reserved_attrs_map)
+        self.log_handler.setFormatter(formatter)
+        self.log.info("message")
+
+        msg = self.buffer.getvalue()
+        self.assertEqual(msg, '{"error.type": null, "error.message": null, "log.origin.function": "test_rename_reserved_attrs", "log.level": "INFO", "log.origin.file.name": "test_jsonlogger", "process.name": "MainProcess", "process.thread.name": "MainThread", "log.message": "message"}\n')
+
+    def test_merge_record_extra(self):
+        record = logging.LogRecord("name", level=1, pathname="", lineno=1, msg="Some message", args=None, exc_info=None)
+        output = jsonlogger.merge_record_extra(record, target=dict(foo="bar"), reserved=[])
+        self.assertIn("foo", output)
+        self.assertIn("msg", output)
+        self.assertEqual(output["foo"], "bar")
+        self.assertEqual(output["msg"], "Some message")
+
+
+if __name__ == '__main__':
+    if len(sys.argv[1:]) > 0:
+        if sys.argv[1] == 'xml':
+            testSuite = unittest.TestLoader().loadTestsFromTestCase(
+                TestJsonLogger)
+            xmlrunner.XMLTestRunner(output='reports').run(testSuite)
+    else:
+        unittest.main()
diff --git a/tests/tests.py b/tests/tests.py
deleted file mode 100644
index 9ca4261..0000000
--- a/tests/tests.py
+++ /dev/null
@@ -1,241 +0,0 @@
-# -*- coding: utf-8 -*-
-import unittest
-import unittest.mock
-import logging
-import json
-import sys
-import traceback
-import random
-
-try:
-    import xmlrunner  # noqa
-except ImportError:
-    pass
-
-try:
-    from StringIO import StringIO  # noqa
-except ImportError:
-    # Python 3 Support
-    from io import StringIO
-
-sys.path.append('src/python-json-logger')
-from pythonjsonlogger import jsonlogger
-import datetime
-
-
-class TestJsonLogger(unittest.TestCase):
-    def setUp(self):
-        self.logger = logging.getLogger("logging-test-{}".format(random.randint(1, 101)))
-        self.logger.setLevel(logging.DEBUG)
-        self.buffer = StringIO()
-
-        self.logHandler = logging.StreamHandler(self.buffer)
-        self.logger.addHandler(self.logHandler)
-
-    def testDefaultFormat(self):
-        fr = jsonlogger.JsonFormatter()
-        self.logHandler.setFormatter(fr)
-
-        msg = "testing logging format"
-        self.logger.info(msg)
-        logJson = json.loads(self.buffer.getvalue())
-
-        self.assertEqual(logJson["message"], msg)
-
-    def testRenameBaseField(self):
-        fr = jsonlogger.JsonFormatter(rename_fields={'message': '@message'})
-        self.logHandler.setFormatter(fr)
-
-        msg = "testing logging format"
-        self.logger.info(msg)
-        logJson = json.loads(self.buffer.getvalue())
-
-        self.assertEqual(logJson["@message"], msg)
-
-    def testFormatKeys(self):
-        supported_keys = [
-            'asctime',
-            'created',
-            'filename',
-            'funcName',
-            'levelname',
-            'levelno',
-            'lineno',
-            'module',
-            'msecs',
-            'message',
-            'name',
-            'pathname',
-            'process',
-            'processName',
-            'relativeCreated',
-            'thread',
-            'threadName'
-        ]
-
-        log_format = lambda x: ['%({0:s})s'.format(i) for i in x]
-        custom_format = ' '.join(log_format(supported_keys))
-
-        fr = jsonlogger.JsonFormatter(custom_format)
-        self.logHandler.setFormatter(fr)
-
-        msg = "testing logging format"
-        self.logger.info(msg)
-        log_msg = self.buffer.getvalue()
-        log_json = json.loads(log_msg)
-
-        for supported_key in supported_keys:
-            if supported_key in log_json:
-                self.assertTrue(True)
-
-    def testUnknownFormatKey(self):
-        fr = jsonlogger.JsonFormatter('%(unknown_key)s %(message)s')
-
-        self.logHandler.setFormatter(fr)
-        msg = "testing unknown logging format"
-        try:
-            self.logger.info(msg)
-        except:
-            self.assertTrue(False, "Should succeed")
-
-    def testLogADict(self):
-        fr = jsonlogger.JsonFormatter()
-        self.logHandler.setFormatter(fr)
-
-        msg = {"text": "testing logging", "num": 1, 5: "9",
-               "nested": {"more": "data"}}
-        self.logger.info(msg)
-        logJson = json.loads(self.buffer.getvalue())
-        self.assertEqual(logJson.get("text"), msg["text"])
-        self.assertEqual(logJson.get("num"), msg["num"])
-        self.assertEqual(logJson.get("5"), msg[5])
-        self.assertEqual(logJson.get("nested"), msg["nested"])
-        self.assertEqual(logJson["message"], None)
-
-    def testLogExtra(self):
-        fr = jsonlogger.JsonFormatter()
-        self.logHandler.setFormatter(fr)
-
-        extra = {"text": "testing logging", "num": 1, 5: "9",
-                 "nested": {"more": "data"}}
-        self.logger.info("hello", extra=extra)
-        logJson = json.loads(self.buffer.getvalue())
-        self.assertEqual(logJson.get("text"), extra["text"])
-        self.assertEqual(logJson.get("num"), extra["num"])
-        self.assertEqual(logJson.get("5"), extra[5])
-        self.assertEqual(logJson.get("nested"), extra["nested"])
-        self.assertEqual(logJson["message"], "hello")
-
-    def testJsonDefaultEncoder(self):
-        fr = jsonlogger.JsonFormatter()
-        self.logHandler.setFormatter(fr)
-
-        msg = {"adate": datetime.datetime(1999, 12, 31, 23, 59),
-               "otherdate": datetime.date(1789, 7, 14),
-               "otherdatetime": datetime.datetime(1789, 7, 14, 23, 59),
-               "otherdatetimeagain": datetime.datetime(1900, 1, 1)}
-        self.logger.info(msg)
-        logJson = json.loads(self.buffer.getvalue())
-        self.assertEqual(logJson.get("adate"), "1999-12-31T23:59:00")
-        self.assertEqual(logJson.get("otherdate"), "1789-07-14")
-        self.assertEqual(logJson.get("otherdatetime"), "1789-07-14T23:59:00")
-        self.assertEqual(logJson.get("otherdatetimeagain"),
-                         "1900-01-01T00:00:00")
-
-    @unittest.mock.patch('time.time', return_value=1500000000.0)
-    def testJsonDefaultEncoderWithTimestamp(self, time_mock):
-        fr = jsonlogger.JsonFormatter(timestamp=True)
-        self.logHandler.setFormatter(fr)
-
-        self.logger.info("Hello")
-
-        self.assertTrue(time_mock.called)
-        logJson = json.loads(self.buffer.getvalue())
-        self.assertEqual(logJson.get("timestamp"), "2017-07-14T02:40:00+00:00")
-
-    def testJsonCustomDefault(self):
-        def custom(o):
-            return "very custom"
-        fr = jsonlogger.JsonFormatter(json_default=custom)
-        self.logHandler.setFormatter(fr)
-
-        msg = {"adate": datetime.datetime(1999, 12, 31, 23, 59),
-               "normal": "value"}
-        self.logger.info(msg)
-        logJson = json.loads(self.buffer.getvalue())
-        self.assertEqual(logJson.get("adate"), "very custom")
-        self.assertEqual(logJson.get("normal"), "value")
-
-    def testJsonCustomLogicAddsField(self):
-        class CustomJsonFormatter(jsonlogger.JsonFormatter):
-
-            def process_log_record(self, log_record):
-                log_record["custom"] = "value"
-                # Old Style "super" since Python 2.6's logging.Formatter is old
-                # style
-                return jsonlogger.JsonFormatter.process_log_record(self, log_record)
-
-        self.logHandler.setFormatter(CustomJsonFormatter())
-        self.logger.info("message")
-        logJson = json.loads(self.buffer.getvalue())
-        self.assertEqual(logJson.get("custom"), "value")
-
-    def testExcInfo(self):
-        fr = jsonlogger.JsonFormatter()
-        self.logHandler.setFormatter(fr)
-        try:
-            raise Exception('test')
-        except Exception:
-
-            self.logger.exception("hello")
-
-            expected_value = traceback.format_exc()
-            # Formatter removes trailing new line
-            if expected_value.endswith('\n'):
-                expected_value = expected_value[:-1]
-
-        logJson = json.loads(self.buffer.getvalue())
-        self.assertEqual(logJson.get("exc_info"), expected_value)
-
-    def testEnsureAsciiTrue(self):
-        fr = jsonlogger.JsonFormatter()
-        self.logHandler.setFormatter(fr)
-        self.logger.info('Привет')
-        msg = self.buffer.getvalue().split('"message": "', 1)[1].split('"', 1)[0]
-        self.assertEqual(msg, r"\u041f\u0440\u0438\u0432\u0435\u0442")
-
-    def testEnsureAsciiFalse(self):
-        fr = jsonlogger.JsonFormatter(json_ensure_ascii=False)
-        self.logHandler.setFormatter(fr)
-        self.logger.info('Привет')
-        msg = self.buffer.getvalue().split('"message": "', 1)[1].split('"', 1)[0]
-        self.assertEqual(msg, "Привет")
-
-    def testCustomObjectSerialization(self):
-        def encode_complex(z):
-            if isinstance(z, complex):
-                return (z.real, z.imag)
-            else:
-                type_name = z.__class__.__name__
-                raise TypeError("Object of type '{}' is no JSON serializable".format(type_name))
-
-        formatter = jsonlogger.JsonFormatter(json_default=encode_complex,
-                                             json_encoder=json.JSONEncoder)
-        self.logHandler.setFormatter(formatter)
-
-        value = {
-            "special": complex(3, 8),
-        }
-
-        self.logger.info(" message", extra=value)
-        msg = self.buffer.getvalue()
-        self.assertEqual(msg, "{\"message\": \" message\", \"special\": [3.0, 8.0]}\n")
-
-if __name__ == '__main__':
-    if len(sys.argv[1:]) > 0:
-        if sys.argv[1] == 'xml':
-            testSuite = unittest.TestLoader().loadTestsFromTestCase(
-                TestJsonLogger)
-            xmlrunner.XMLTestRunner(output='reports').run(testSuite)
-    else:
-        unittest.main()

More details

Full run details

Historical runs