python-json-logger 4.1.0__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {python_json_logger-4.1.0/src/python_json_logger.egg-info → python_json_logger-4.2.0}/PKG-INFO +1 -20
  2. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/pyproject.toml +4 -3
  3. {python_json_logger-4.1.0 → python_json_logger-4.2.0/src/python_json_logger.egg-info}/PKG-INFO +1 -20
  4. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/python_json_logger.egg-info/SOURCES.txt +0 -1
  5. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/core.py +26 -17
  6. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/tests/test_formatters.py +175 -0
  7. python_json_logger-4.1.0/src/python_json_logger.egg-info/requires.txt +0 -22
  8. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/LICENSE +0 -0
  9. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/MANIFEST.in +0 -0
  10. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/NOTICE +0 -0
  11. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/README.md +0 -0
  12. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/setup.cfg +0 -0
  13. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/python_json_logger.egg-info/dependency_links.txt +0 -0
  14. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/python_json_logger.egg-info/top_level.txt +0 -0
  15. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/__init__.py +0 -0
  16. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/defaults.py +0 -0
  17. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/exception.py +0 -0
  18. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/json.py +0 -0
  19. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/jsonlogger.py +0 -0
  20. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/msgspec.py +0 -0
  21. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/orjson.py +0 -0
  22. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/py.typed +0 -0
  23. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/src/pythonjsonlogger/utils.py +0 -0
  24. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/tests/__init__.py +0 -0
  25. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/tests/test_deprecation.py +0 -0
  26. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/tests/test_dictconfig.py +0 -0
  27. {python_json_logger-4.1.0 → python_json_logger-4.2.0}/tests/test_missing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-json-logger
3
- Version: 4.1.0
3
+ Version: 4.2.0
4
4
  Summary: JSON Log Formatter for the Python Logging Package
5
5
  Author-email: Zakaria Zajac <zak@madzak.com>, Nicholas Hairs <info+python-json-logger@nicholashairs.com>
6
6
  Maintainer-email: Nicholas Hairs <info+python-json-logger@nicholashairs.com>
@@ -21,25 +21,6 @@ Classifier: Typing :: Typed
21
21
  Requires-Python: >=3.10
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Provides-Extra: dev
25
- Requires-Dist: orjson; implementation_name != "pypy" and extra == "dev"
26
- Requires-Dist: msgspec; implementation_name != "pypy" and extra == "dev"
27
- Requires-Dist: validate-pyproject[all]; extra == "dev"
28
- Requires-Dist: black; extra == "dev"
29
- Requires-Dist: pylint; extra == "dev"
30
- Requires-Dist: mypy; extra == "dev"
31
- Requires-Dist: pytest; extra == "dev"
32
- Requires-Dist: freezegun; extra == "dev"
33
- Requires-Dist: tzdata; extra == "dev"
34
- Requires-Dist: build; extra == "dev"
35
- Requires-Dist: mkdocs; extra == "dev"
36
- Requires-Dist: mkdocs-material>=8.5; extra == "dev"
37
- Requires-Dist: mkdocs-awesome-pages-plugin; extra == "dev"
38
- Requires-Dist: mdx_truly_sane_lists; extra == "dev"
39
- Requires-Dist: mkdocstrings[python]; extra == "dev"
40
- Requires-Dist: mkdocs-gen-files; extra == "dev"
41
- Requires-Dist: mkdocs-literate-nav; extra == "dev"
42
- Requires-Dist: mike; extra == "dev"
43
24
  Dynamic: license-file
44
25
 
45
26
  [![PyPi](https://img.shields.io/pypi/v/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-json-logger"
7
- version = "4.1.0"
7
+ version = "4.2.0"
8
8
  description = "JSON Log Formatter for the Python Logging Package"
9
9
  authors = [
10
10
  {name = "Zakaria Zajac", email = "zak@madzak.com"},
@@ -39,7 +39,7 @@ classifiers = [
39
39
  Homepage = "https://nhairs.github.io/python-json-logger"
40
40
  GitHub = "https://github.com/nhairs/python-json-logger"
41
41
 
42
- [project.optional-dependencies]
42
+ [dependency-groups]
43
43
  dev = [
44
44
  ## Optional but required for dev
45
45
  "orjson;implementation_name!='pypy'",
@@ -48,7 +48,8 @@ dev = [
48
48
  "validate-pyproject[all]",
49
49
  "black",
50
50
  "pylint",
51
- "mypy",
51
+ "mypy<2; implementation_name == 'pypy' and python_version < '3.11'",
52
+ "mypy; implementation_name != 'pypy' or python_version >= '3.11'",
52
53
  ## Test
53
54
  "pytest",
54
55
  "freezegun",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-json-logger
3
- Version: 4.1.0
3
+ Version: 4.2.0
4
4
  Summary: JSON Log Formatter for the Python Logging Package
5
5
  Author-email: Zakaria Zajac <zak@madzak.com>, Nicholas Hairs <info+python-json-logger@nicholashairs.com>
6
6
  Maintainer-email: Nicholas Hairs <info+python-json-logger@nicholashairs.com>
@@ -21,25 +21,6 @@ Classifier: Typing :: Typed
21
21
  Requires-Python: >=3.10
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Provides-Extra: dev
25
- Requires-Dist: orjson; implementation_name != "pypy" and extra == "dev"
26
- Requires-Dist: msgspec; implementation_name != "pypy" and extra == "dev"
27
- Requires-Dist: validate-pyproject[all]; extra == "dev"
28
- Requires-Dist: black; extra == "dev"
29
- Requires-Dist: pylint; extra == "dev"
30
- Requires-Dist: mypy; extra == "dev"
31
- Requires-Dist: pytest; extra == "dev"
32
- Requires-Dist: freezegun; extra == "dev"
33
- Requires-Dist: tzdata; extra == "dev"
34
- Requires-Dist: build; extra == "dev"
35
- Requires-Dist: mkdocs; extra == "dev"
36
- Requires-Dist: mkdocs-material>=8.5; extra == "dev"
37
- Requires-Dist: mkdocs-awesome-pages-plugin; extra == "dev"
38
- Requires-Dist: mdx_truly_sane_lists; extra == "dev"
39
- Requires-Dist: mkdocstrings[python]; extra == "dev"
40
- Requires-Dist: mkdocs-gen-files; extra == "dev"
41
- Requires-Dist: mkdocs-literate-nav; extra == "dev"
42
- Requires-Dist: mike; extra == "dev"
43
24
  Dynamic: license-file
44
25
 
45
26
  [![PyPi](https://img.shields.io/pypi/v/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
@@ -6,7 +6,6 @@ pyproject.toml
6
6
  src/python_json_logger.egg-info/PKG-INFO
7
7
  src/python_json_logger.egg-info/SOURCES.txt
8
8
  src/python_json_logger.egg-info/dependency_links.txt
9
- src/python_json_logger.egg-info/requires.txt
10
9
  src/python_json_logger.egg-info/top_level.txt
11
10
  src/pythonjsonlogger/__init__.py
12
11
  src/pythonjsonlogger/core.py
@@ -61,7 +61,9 @@ if sys.version_info >= (3, 12):
61
61
  RESERVED_ATTRS.sort()
62
62
 
63
63
 
64
- STYLE_STRING_TEMPLATE_REGEX = re.compile(r"\$\{(.+?)\}", re.IGNORECASE) # $ style
64
+ STYLE_STRING_TEMPLATE_REGEX = re.compile(
65
+ r"\$(?:\$|\{(?P<braced>.+?)\}|(?P<named>[_a-z][_a-z0-9]*))", re.IGNORECASE
66
+ ) # $ style
65
67
  STYLE_STRING_FORMAT_REGEX = re.compile(r"\{(.+?)\}", re.IGNORECASE) # { style
66
68
  STYLE_PERCENT_REGEX = re.compile(r"%\((.+?)\)", re.IGNORECASE) # % style
67
69
 
@@ -111,10 +113,6 @@ class BaseJsonFormatter(logging.Formatter):
111
113
  Must not be used directly.
112
114
 
113
115
  *New in 3.1*
114
-
115
- *Changed in 3.2*: `defaults` argument is no longer ignored.
116
-
117
- *Added in 3.3*: `exc_info_as_array` and `stack_info_as_array` options are added.
118
116
  """
119
117
 
120
118
  _style: logging.PercentStyle | str # type: ignore[assignment]
@@ -172,16 +170,19 @@ class BaseJsonFormatter(logging.Formatter):
172
170
  missing fields. The original behaviour, missing fields have a value of `None`, is still
173
171
  available by setting `rename_fields_keep_missing` to `True`.
174
172
 
173
+ *Changed in 3.2*: `defaults` argument is no longer ignored.
174
+
175
+ *Added in 3.3*: `exc_info_as_array` and `stack_info_as_array` options are added.
176
+
175
177
  *Added in 4.0*:
176
178
 
177
- - `fmt` now supports comma seperated lists (`style=","`). Note that this style is specific
179
+ - `fmt` now supports comma separated lists (`style=","`). Note that this style is specific
178
180
  to `python-json-logger` and thus care should be taken to not to pass this format to other
179
181
  logging Formatter implementations.
180
182
  - `fmt` now supports sequences of strings (e.g. lists and tuples) of field names.
181
183
  """
182
184
  ## logging.Formatter compatibility
183
185
  ## ---------------------------------------------------------------------
184
- # Note: validate added in python 3.8, defaults added in 3.10
185
186
  if fmt is None or isinstance(fmt, str):
186
187
  if style in logging._STYLES:
187
188
  _style = logging._STYLES[style][0](fmt) # type: ignore[operator]
@@ -236,14 +237,20 @@ class BaseJsonFormatter(logging.Formatter):
236
237
  def format(self, record: logging.LogRecord) -> str:
237
238
  """Formats a log record and serializes to json
238
239
 
240
+ When `record.msg` is a `dict`, adding `exc_info` and `stack_info` does not
241
+ modify the caller's dict.
242
+
239
243
  Args:
240
244
  record: the record to format
245
+
246
+ *Changed in 4.2.0*: a `dict` `record.msg` is copied instead of modified
247
+ in place.
241
248
  """
242
249
  message_dict: dict[str, Any] = {}
243
250
  # TODO: logging.LogRecord.msg and logging.LogRecord.message in typeshed
244
251
  # are always type of str. We shouldn't need to override that.
245
252
  if isinstance(record.msg, dict):
246
- message_dict = record.msg
253
+ message_dict = record.msg.copy()
247
254
  record.message = ""
248
255
  else:
249
256
  record.message = record.getMessage()
@@ -296,20 +303,22 @@ class BaseJsonFormatter(logging.Formatter):
296
303
  raise ValueError(f"Style {self._style!r} is not supported")
297
304
 
298
305
  if isinstance(self._style, logging.StringTemplateStyle):
299
- formatter_style_pattern = STYLE_STRING_TEMPLATE_REGEX
306
+ # String templates support both ${name} and $name, and $$ is an escaped literal
307
+ return [
308
+ match.group("braced") or match.group("named")
309
+ for match in STYLE_STRING_TEMPLATE_REGEX.finditer(self._fmt)
310
+ if match.group("braced") or match.group("named")
311
+ ]
300
312
 
301
- elif isinstance(self._style, logging.StrFormatStyle):
302
- formatter_style_pattern = STYLE_STRING_FORMAT_REGEX
313
+ if isinstance(self._style, logging.StrFormatStyle):
314
+ return STYLE_STRING_FORMAT_REGEX.findall(self._fmt)
303
315
 
304
- elif isinstance(self._style, logging.PercentStyle):
316
+ if isinstance(self._style, logging.PercentStyle):
305
317
  # PercentStyle is parent class of StringTemplateStyle and StrFormatStyle
306
318
  # so it must be checked last.
307
- formatter_style_pattern = STYLE_PERCENT_REGEX
308
-
309
- else:
310
- raise ValueError(f"Style {self._style!r} is not supported")
319
+ return STYLE_PERCENT_REGEX.findall(self._fmt)
311
320
 
312
- return formatter_style_pattern.findall(self._fmt)
321
+ raise ValueError(f"Style {self._style!r} is not supported")
313
322
 
314
323
  def serialize_log_record(self, log_data: LogData) -> str:
315
324
  """Returns the final representation of the data to be logged
@@ -167,6 +167,22 @@ def test_percentage_format(env: LoggingEnvironment, class_: type[BaseJsonFormatt
167
167
  return
168
168
 
169
169
 
170
+ @pytest.mark.parametrize("class_", ALL_FORMATTERS)
171
+ def test_string_template_format(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
172
+ # Note: string templates support both $name and ${name}, and $$ is an escaped literal $
173
+ env.set_formatter(
174
+ class_("$$literal $levelname ${message} $filename ${lineno} $asctime", style="$")
175
+ )
176
+
177
+ msg = "testing logging format"
178
+ env.logger.info(msg)
179
+ log_json = env.load_json()
180
+
181
+ assert log_json["message"] == msg
182
+ assert log_json.keys() == {"levelname", "message", "filename", "lineno", "asctime"}
183
+ return
184
+
185
+
170
186
  @pytest.mark.parametrize("class_", ALL_FORMATTERS)
171
187
  def test_comma_format(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
172
188
  # Note: we have double comma `,,` to test handling "empty" names
@@ -393,6 +409,23 @@ def test_log_dict_defaults(env: LoggingEnvironment, class_: type[BaseJsonFormatt
393
409
  return
394
410
 
395
411
 
412
+ @pytest.mark.parametrize("class_", ALL_FORMATTERS)
413
+ def test_log_dict_not_modified(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
414
+ env.set_formatter(class_())
415
+
416
+ msg = {"text": "testing logging", "nested": {"more": "data"}}
417
+ try:
418
+ raise ValueError("test")
419
+ except ValueError:
420
+ env.logger.exception(msg, stack_info=True)
421
+ log_json = env.load_json()
422
+
423
+ assert log_json["exc_info"]
424
+ assert log_json["stack_info"]
425
+ assert msg == {"text": "testing logging", "nested": {"more": "data"}}
426
+ return
427
+
428
+
396
429
  @pytest.mark.parametrize("class_", ALL_FORMATTERS)
397
430
  def test_log_extra(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
398
431
  env.set_formatter(class_())
@@ -681,6 +714,148 @@ def test_exc_info_as_array_no_exc_info(env: LoggingEnvironment, class_: type[Bas
681
714
  return
682
715
 
683
716
 
717
+ @pytest.mark.skipif(
718
+ sys.version_info < (3, 11), reason="Exception notes require Python 3.11 or higher"
719
+ )
720
+ @pytest.mark.parametrize("class_", ALL_FORMATTERS)
721
+ def test_exception_notes(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
722
+ env.set_formatter(class_())
723
+
724
+ try:
725
+ err = ValueError("my error")
726
+ err.add_note("this is a note")
727
+ raise err
728
+ except ValueError:
729
+ env.logger.exception("oops")
730
+
731
+ log_json = env.load_json()
732
+ assert "ValueError: my error" in log_json["exc_info"]
733
+ assert "this is a note" in log_json["exc_info"]
734
+ return
735
+
736
+
737
+ @pytest.mark.skipif(
738
+ sys.version_info < (3, 11), reason="Exception notes require Python 3.11 or higher"
739
+ )
740
+ @pytest.mark.parametrize("class_", ALL_FORMATTERS)
741
+ def test_exception_notes_as_array(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
742
+ env.set_formatter(class_(exc_info_as_array=True))
743
+
744
+ try:
745
+ err = ValueError("my error")
746
+ err.add_note("this is a note")
747
+ raise err
748
+ except ValueError:
749
+ env.logger.exception("oops")
750
+
751
+ log_json = env.load_json()
752
+ assert isinstance(log_json["exc_info"], list)
753
+ full_traceback = "\n".join(log_json["exc_info"])
754
+ assert "ValueError: my error" in full_traceback
755
+ assert "this is a note" in full_traceback
756
+ return
757
+
758
+
759
+ @pytest.mark.skipif(
760
+ sys.version_info < (3, 11), reason="Exception groups require Python 3.11 or higher"
761
+ )
762
+ @pytest.mark.parametrize("class_", ALL_FORMATTERS)
763
+ def test_exception_groups(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
764
+ env.set_formatter(class_())
765
+
766
+ try:
767
+ raise ExceptionGroup("my group", [ValueError("err 1"), TypeError("err 2")])
768
+ except ExceptionGroup:
769
+ env.logger.exception("oops")
770
+
771
+ log_json = env.load_json()
772
+ assert "ExceptionGroup: my group" in log_json["exc_info"]
773
+ assert "ValueError: err 1" in log_json["exc_info"]
774
+ assert "TypeError: err 2" in log_json["exc_info"]
775
+ return
776
+
777
+
778
+ @pytest.mark.skipif(
779
+ sys.version_info < (3, 11), reason="Exception groups require Python 3.11 or higher"
780
+ )
781
+ @pytest.mark.parametrize("class_", ALL_FORMATTERS)
782
+ def test_exception_groups_as_array(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
783
+ env.set_formatter(class_(exc_info_as_array=True))
784
+
785
+ try:
786
+ raise ExceptionGroup("my group", [ValueError("err 1"), TypeError("err 2")])
787
+ except ExceptionGroup:
788
+ env.logger.exception("oops")
789
+
790
+ log_json = env.load_json()
791
+ assert isinstance(log_json["exc_info"], list)
792
+ full_traceback = "\n".join(log_json["exc_info"])
793
+ assert "ExceptionGroup: my group" in full_traceback
794
+ assert "ValueError: err 1" in full_traceback
795
+ assert "TypeError: err 2" in full_traceback
796
+ return
797
+
798
+
799
+ @pytest.mark.skipif(
800
+ sys.version_info < (3, 11), reason="Exception groups and notes require Python 3.11 or higher"
801
+ )
802
+ @pytest.mark.parametrize("class_", ALL_FORMATTERS)
803
+ def test_exception_groups_with_notes(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
804
+ env.set_formatter(class_())
805
+
806
+ try:
807
+ err1 = ValueError("err 1")
808
+ err1.add_note("note 1")
809
+ err2 = TypeError("err 2")
810
+ err2.add_note("note 2")
811
+ eg = ExceptionGroup("my group", [err1, err2])
812
+ eg.add_note("group note")
813
+ raise eg
814
+ except ExceptionGroup:
815
+ env.logger.exception("oops")
816
+
817
+ log_json = env.load_json()
818
+ assert "ExceptionGroup: my group" in log_json["exc_info"]
819
+ assert "group note" in log_json["exc_info"]
820
+ assert "ValueError: err 1" in log_json["exc_info"]
821
+ assert "note 1" in log_json["exc_info"]
822
+ assert "TypeError: err 2" in log_json["exc_info"]
823
+ assert "note 2" in log_json["exc_info"]
824
+ return
825
+
826
+
827
+ @pytest.mark.skipif(
828
+ sys.version_info < (3, 11), reason="Exception groups and notes require Python 3.11 or higher"
829
+ )
830
+ @pytest.mark.parametrize("class_", ALL_FORMATTERS)
831
+ def test_exception_groups_with_notes_as_array(
832
+ env: LoggingEnvironment, class_: type[BaseJsonFormatter]
833
+ ):
834
+ env.set_formatter(class_(exc_info_as_array=True))
835
+
836
+ try:
837
+ err1 = ValueError("err 1")
838
+ err1.add_note("note 1")
839
+ err2 = TypeError("err 2")
840
+ err2.add_note("note 2")
841
+ eg = ExceptionGroup("my group", [err1, err2])
842
+ eg.add_note("group note")
843
+ raise eg
844
+ except ExceptionGroup:
845
+ env.logger.exception("oops")
846
+
847
+ log_json = env.load_json()
848
+ assert isinstance(log_json["exc_info"], list)
849
+ full_traceback = "\n".join(log_json["exc_info"])
850
+ assert "ExceptionGroup: my group" in full_traceback
851
+ assert "group note" in full_traceback
852
+ assert "ValueError: err 1" in full_traceback
853
+ assert "note 1" in full_traceback
854
+ assert "TypeError: err 2" in full_traceback
855
+ assert "note 2" in full_traceback
856
+ return
857
+
858
+
684
859
  @pytest.mark.parametrize("class_", ALL_FORMATTERS)
685
860
  def test_stack_info_as_array(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
686
861
  env.set_formatter(class_(stack_info_as_array=True))
@@ -1,22 +0,0 @@
1
-
2
- [dev]
3
- validate-pyproject[all]
4
- black
5
- pylint
6
- mypy
7
- pytest
8
- freezegun
9
- tzdata
10
- build
11
- mkdocs
12
- mkdocs-material>=8.5
13
- mkdocs-awesome-pages-plugin
14
- mdx_truly_sane_lists
15
- mkdocstrings[python]
16
- mkdocs-gen-files
17
- mkdocs-literate-nav
18
- mike
19
-
20
- [dev:implementation_name != "pypy"]
21
- orjson
22
- msgspec