Repository: Colin-b/logging_json (language: python)

Issue:
Handle extra parameter



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 85e7c1d..b9556b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,1 +1,2 @@
 /.idea/
+/logging_json.egg-info/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d1b54d3..8e38558 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,5 +1,5 @@
 repos:
   - repo: https://github.com/psf/black
-    rev: 20.8b1
+    rev: 21.9b0
     hooks:
-      - id: black
\ No newline at end of file
+      - id: black
diff --git a/.travis.yml b/.travis.yml
index 83286a0..a8ee29e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,8 @@ python:
   - "3.6"
   - "3.7"
   - "3.8"
-  - "3.9-dev"
+  - "3.9"
+  - "3.10-dev"
 install:
   - pip install .[testing]
 script:
@@ -13,4 +14,4 @@ deploy:
   username: __token__
   edge: true
   distributions: "sdist bdist_wheel"
-  skip_existing: true
\ No newline at end of file
+  skip_existing: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e2155a7..041478e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,9 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+## [0.1.0] - 2021-10-04
+### Fixed
+- Handle `extra` logging parameter.
+
 ## [0.0.1] - 2020-10-15
 ### Added
 - Public release.
 
-[Unreleased]: https://github.com/Colin-b/healthpy/compare/v0.0.1...HEAD
+[Unreleased]: https://github.com/Colin-b/healthpy/compare/v0.1.0...HEAD
+[0.1.0]: https://github.com/Colin-b/healthpy/compare/v0.0.1...v0.1.0
 [0.0.1]: https://github.com/Colin-b/healthpy/releases/tag/v0.0.1
diff --git a/LICENSE b/LICENSE
index 17be259..b70e7e0 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2020 Colin Bounouar
+Copyright (c) 2021 Colin Bounouar
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 03498e9..af45d7f 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 <a href="https://travis-ci.com/Colin-b/logging_json"><img alt="Build status" src="https://api.travis-ci.com/Colin-b/logging_json.svg?branch=master"></a>
 <a href="https://travis-ci.com/Colin-b/logging_json"><img alt="Coverage" src="https://img.shields.io/badge/coverage-100%25-brightgreen"></a>
 <a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
-<a href="https://travis-ci.com/Colin-b/logging_json"><img alt="Number of tests" src="https://img.shields.io/badge/tests-11 passed-blue"></a>
+<a href="https://travis-ci.com/Colin-b/logging_json"><img alt="Number of tests" src="https://img.shields.io/badge/tests-15 passed-blue"></a>
 <a href="https://pypi.org/project/logging_json/"><img alt="Number of downloads" src="https://img.shields.io/pypi/dm/logging_json"></a>
 </p>
 
@@ -55,7 +55,7 @@ The resulting JSON dictionary will be the one you provided (with the [additional
 ### Logging with anything else (such as a string)
 
 Anything not logged using a dictionary will be handled by the standard formatter and it can result in one of the 2 output:
-* A JSON dictionary, if [additional fields](#adding-additional-fields-and-values) are set, with the message available in the `msg` key of the resulting JSON dictionary.
+* A JSON dictionary, if [additional fields](#adding-additional-fields-and-values) are set or if `extra` parameter is used while logging, with the message available in the `msg` key of the resulting JSON dictionary.
 * The formatted record, if no [additional fields](#adding-additional-fields-and-values) are set. 
 
 This handles the usual string logging as in the following:
diff --git a/logging_json/_formatter.py b/logging_json/_formatter.py
index ccecfea..6ee7d92 100644
--- a/logging_json/_formatter.py
+++ b/logging_json/_formatter.py
@@ -1,7 +1,39 @@
 import collections
 import json
 import logging
-from typing import Any
+from typing import Any, Dict
+
+standard_attributes = (
+    "name",
+    "msg",
+    "args",
+    "levelname",
+    "levelno",
+    "pathname",
+    "filename",
+    "module",
+    "exc_info",
+    "exc_text",
+    "stack_info",
+    "lineno",
+    "funcName",
+    "created",
+    "msecs",
+    "relativeCreated",
+    "thread",
+    "threadName",
+    "processName",
+    "process",
+    "message",
+    "asctime",
+)
+
+
+def _extra_attributes(record: logging.LogRecord) -> Dict[str, Any]:
+    return {
+        name: record.__dict__[name]
+        for name in set(record.__dict__).difference(standard_attributes)
+    }
 
 
 def _value(record: logging.LogRecord, field_name_or_value: Any) -> Any:
@@ -17,7 +49,7 @@ def _value(record: logging.LogRecord, field_name_or_value: Any) -> Any:
 
 
 class JSONFormatter(logging.Formatter):
-    def __init__(self, *args, fields=None, **kwargs):
+    def __init__(self, *args, fields: Dict[str, Any] = None, **kwargs):
         # Allow to provide any formatter setting (useful to provide a custom date format)
         super().__init__(*args, **kwargs)
         self.fields = fields or {}
@@ -36,6 +68,8 @@ def format(self, record: logging.LogRecord):
         else:
             message["msg"] = super().formatMessage(record)
 
+        message.update(_extra_attributes(record))
+
         if record.exc_info:
             message["exception"] = {
                 "type": record.exc_info[0].__name__,
diff --git a/logging_json/version.py b/logging_json/version.py
index e973d31..43c94ee 100644
--- a/logging_json/version.py
+++ b/logging_json/version.py
@@ -3,4 +3,4 @@
 # Major should be incremented in case there is a breaking change. (eg: 2.5.8 -> 3.0.0)
 # Minor should be incremented in case there is an enhancement. (eg: 2.5.8 -> 2.6.0)
 # Patch should be incremented in case there is a bug fix. (eg: 2.5.8 -> 2.5.9)
-__version__ = "0.0.1"
+__version__ = "0.1.0"


Test patch:
diff --git a/tests/test_formatter.py b/tests/test_formatter.py
index 23dc62a..313a5e0 100644
--- a/tests/test_formatter.py
+++ b/tests/test_formatter.py
@@ -33,6 +33,18 @@ def test_str_with_args_message(caplog):
     assert fmt(caplog) == "message 1"
 
 
+def test_str_with_extra_message(caplog):
+    caplog.set_level("INFO")
+    logging.info("message 1", extra={"key1": "value 1"})
+    assert dict_fmt(caplog) == {"msg": "message 1", "key1": "value 1"}
+
+
+def test_str_with_args_and_extra_message(caplog):
+    caplog.set_level("INFO")
+    logging.info("message %s", "1", extra={"key1": "value 1"})
+    assert dict_fmt(caplog) == {"msg": "message 1", "key1": "value 1"}
+
+
 def test_dict_message_with_asctime(caplog, monkeypatch):
     monkeypatch.setattr(time, "time", lambda: 1599736353.0076675)
     caplog.set_level("INFO")
@@ -60,6 +72,15 @@ def test_str_with_args_message_with_asctime(caplog, monkeypatch):
     assert actual == {"msg": "message 1"}
 
 
+def test_str_with_args_and_extra_message_with_asctime(caplog, monkeypatch):
+    monkeypatch.setattr(time, "time", lambda: 1599736353.0076675)
+    caplog.set_level("INFO")
+    logging.info("message %s", "1", extra={"key1": "value 1"})
+    actual = dict_fmt(caplog, fields={"date_time": "asctime"})
+    assert time.strptime(actual.pop("date_time"), "%Y-%m-%d %H:%M:%S,007")
+    assert actual == {"msg": "message 1", "key1": "value 1"}
+
+
 def test_dict_message_at_exception_level(caplog):
     caplog.set_level("INFO")
     try:
@@ -159,6 +180,26 @@ def test_documented_record_attributes(caplog, monkeypatch):
     }
 
 
+def test_with_extra_in_fields_and_message(caplog):
+    caplog.set_level("INFO")
+    logging.info("message 1", extra={"key1": "value 1"})
+    assert (
+        dict_fmt(
+            caplog,
+            fields={
+                "extra": "key1",
+                "key2": "value 2",
+            },
+        )
+        == {
+            "extra": "value 1",
+            "key1": "value 1",
+            "key2": "value 2",
+            "msg": "message 1",
+        }
+    )
+
+
 def fmt(caplog, *formatter_args, **formatter_kwargs) -> str:
     return logging_json.JSONFormatter(*formatter_args, **formatter_kwargs).format(
         caplog.records[0]