python-json-logger 4.1.0__py3-none-any.whl → 4.3.0.dev2__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-json-logger
3
- Version: 4.1.0
3
+ Version: 4.3.0.dev2
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>
@@ -16,30 +16,12 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: 3.15
19
20
  Classifier: Topic :: System :: Logging
20
21
  Classifier: Typing :: Typed
21
22
  Requires-Python: >=3.10
22
23
  Description-Content-Type: text/markdown
23
24
  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
25
  Dynamic: license-file
44
26
 
45
27
  [![PyPi](https://img.shields.io/pypi/v/python-json-logger.svg)](https://pypi.python.org/pypi/python-json-logger/)
@@ -1,7 +1,7 @@
1
- python_json_logger-4.1.0.dist-info/licenses/LICENSE,sha256=GOqVF546Xg4k62zhbED7--IxM8JQKTOi91-KPhoFW1Q,1329
1
+ python_json_logger-4.3.0.dev2.dist-info/licenses/LICENSE,sha256=GOqVF546Xg4k62zhbED7--IxM8JQKTOi91-KPhoFW1Q,1329
2
2
  pythonjsonlogger/__init__.py,sha256=2QcveuId10CyXff_BtJ_6kXIarHT5oq66XHHMFiaE8s,419
3
- pythonjsonlogger/core.py,sha256=27H5Rd1vZsWfFYbtwqoYEQTN8GSqxeZVrAN-_pIcd8A,15870
4
- pythonjsonlogger/defaults.py,sha256=muj4QzHgUMJH0UwRvgroHKSRfQgHcF9E-EJXhVzAi98,6461
3
+ pythonjsonlogger/core.py,sha256=T02iURAubnMTk0PO0HUOckS93eMfAFapSZcFPXJqB64,17102
4
+ pythonjsonlogger/defaults.py,sha256=VR_Wl092yRjwS0mREjuNn_W4H4sAjtcZAIYFBMYNHe0,6770
5
5
  pythonjsonlogger/exception.py,sha256=r3DXDk7TThscnMVNPpVRaRSFHTnY-ygea6nn-FgjwsI,804
6
6
  pythonjsonlogger/json.py,sha256=6aJSqd2BeysAHkQkxb7dPgFpqsFRf4rvWYgaWF2MEC0,4064
7
7
  pythonjsonlogger/jsonlogger.py,sha256=sfltkYGwRhRvqcpT-kxlPfcMuOc4ngEa4W4NVTiBpMI,417
@@ -9,7 +9,7 @@ pythonjsonlogger/msgspec.py,sha256=tdFes9rLrdns9I_C5ascK9a265iNc4UccLPzc8JbFDI,2
9
9
  pythonjsonlogger/orjson.py,sha256=XWM-jyl6g_40Em8kt188EOspD872xHvYy8aS_CLlssw,2354
10
10
  pythonjsonlogger/py.typed,sha256=4RLptUHQuSqzK6CDbigff8uvWQjwPPQMxikWPkV4OtA,80
11
11
  pythonjsonlogger/utils.py,sha256=qr04nb61TkVw7cJTXAtLBfyH_NBvyYUlR_mehH76-RM,1126
12
- python_json_logger-4.1.0.dist-info/METADATA,sha256=3m2R5zYhsgprnQkI5JW0VHQl7B2r3maSYjGeegZLI10,3739
13
- python_json_logger-4.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
14
- python_json_logger-4.1.0.dist-info/top_level.txt,sha256=9G-OsTkbwPgM8t-bXKPmWDBRZv9cbzLIiEDE5EnGk2I,17
15
- python_json_logger-4.1.0.dist-info/RECORD,,
12
+ python_json_logger-4.3.0.dev2.dist-info/METADATA,sha256=q4IajnR8q8pXVltRYOx5V3-7FPkoGkECHgo9XbSb9AI,2922
13
+ python_json_logger-4.3.0.dev2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
14
+ python_json_logger-4.3.0.dev2.dist-info/top_level.txt,sha256=9G-OsTkbwPgM8t-bXKPmWDBRZv9cbzLIiEDE5EnGk2I,17
15
+ python_json_logger-4.3.0.dev2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.1)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
pythonjsonlogger/core.py CHANGED
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
  from datetime import datetime, timezone
10
10
  import logging
11
11
  import re
12
+ import string
12
13
  import sys
13
14
  from typing import TypeAlias, Any
14
15
  from collections.abc import Container, Sequence
@@ -61,9 +62,10 @@ if sys.version_info >= (3, 12):
61
62
  RESERVED_ATTRS.sort()
62
63
 
63
64
 
64
- STYLE_STRING_TEMPLATE_REGEX = re.compile(r"\$\{(.+?)\}", re.IGNORECASE) # $ style
65
- STYLE_STRING_FORMAT_REGEX = re.compile(r"\{(.+?)\}", re.IGNORECASE) # { style
66
- STYLE_PERCENT_REGEX = re.compile(r"%\((.+?)\)", re.IGNORECASE) # % style
65
+ STYLE_STRING_TEMPLATE_REGEX = re.compile(
66
+ r"\$(?:\$|\{(?P<braced>.+?)\}|(?P<named>[_a-z][_a-z0-9]*))", re.IGNORECASE
67
+ ) # $ style
68
+ STYLE_PERCENT_REGEX = re.compile(r"%(?:%|\((?P<named>.+?)\))", re.IGNORECASE) # % style
67
69
 
68
70
  ## Type Aliases
69
71
  ## -----------------------------------------------------------------------------
@@ -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]
@@ -155,12 +153,14 @@ class BaseJsonFormatter(logging.Formatter):
155
153
  rename_fields_keep_missing: When renaming fields, include missing fields in the output.
156
154
  static_fields: an optional dict, used to add fields with static values to all logs
157
155
  reserved_attrs: an optional list of fields that will be skipped when
158
- outputting json log record. Defaults to [all log record attributes][pythonjsonlogger.core.RESERVED_ATTRS].
156
+ outputting json log record. Pass a list or tuple, not a string.
157
+ Defaults to [all log record attributes][pythonjsonlogger.core.RESERVED_ATTRS].
159
158
  timestamp: an optional string/boolean field to add a timestamp when
160
159
  outputting the json log record. If string is passed, timestamp will be added
161
160
  to log record using string as key. If True boolean is passed, timestamp key
162
161
  will be "timestamp". Defaults to False/off.
163
- exc_info_as_array: break the exc_info into a list of lines based on line breaks.
162
+ exc_info_as_array: break the exc_info into a list of lines based on line breaks,
163
+ including cached exception text in records received over a transport.
164
164
  stack_info_as_array: break the stack_info into a list of lines based on line breaks.
165
165
 
166
166
  *Changed in 3.1*:
@@ -172,16 +172,19 @@ class BaseJsonFormatter(logging.Formatter):
172
172
  missing fields. The original behaviour, missing fields have a value of `None`, is still
173
173
  available by setting `rename_fields_keep_missing` to `True`.
174
174
 
175
+ *Changed in 3.2*: `defaults` argument is no longer ignored.
176
+
177
+ *Added in 3.3*: `exc_info_as_array` and `stack_info_as_array` options are added.
178
+
175
179
  *Added in 4.0*:
176
180
 
177
- - `fmt` now supports comma seperated lists (`style=","`). Note that this style is specific
181
+ - `fmt` now supports comma separated lists (`style=","`). Note that this style is specific
178
182
  to `python-json-logger` and thus care should be taken to not to pass this format to other
179
183
  logging Formatter implementations.
180
184
  - `fmt` now supports sequences of strings (e.g. lists and tuples) of field names.
181
185
  """
182
186
  ## logging.Formatter compatibility
183
187
  ## ---------------------------------------------------------------------
184
- # Note: validate added in python 3.8, defaults added in 3.10
185
188
  if fmt is None or isinstance(fmt, str):
186
189
  if style in logging._STYLES:
187
190
  _style = logging._STYLES[style][0](fmt) # type: ignore[operator]
@@ -212,8 +215,9 @@ class BaseJsonFormatter(logging.Formatter):
212
215
  ## ---------------------------------------------------------------------
213
216
  self.prefix = prefix
214
217
 
215
- # We recreate the dict in rename_fields and static_fields to support internal/external
216
- # references which require getting the item to do the conversion.
218
+ # Recreate these dicts so DictConfigurator `ext://` / `cfg://` prefixes
219
+ # are converted (ConvertingDict only converts on `__getitem__`) and so
220
+ # later mutation of the caller's dict cannot change formatter state.
217
221
  # For more details see: https://github.com/nhairs/python-json-logger/pull/45
218
222
  self.rename_fields = (
219
223
  {key: rename_fields[key] for key in rename_fields} if rename_fields is not None else {}
@@ -221,29 +225,43 @@ class BaseJsonFormatter(logging.Formatter):
221
225
  self.static_fields = (
222
226
  {key: static_fields[key] for key in static_fields} if static_fields is not None else {}
223
227
  )
228
+ self.defaults = {key: defaults[key] for key in defaults} if defaults is not None else {}
224
229
 
225
230
  self.rename_fields_keep_missing = rename_fields_keep_missing
226
- self.reserved_attrs = set(reserved_attrs if reserved_attrs is not None else RESERVED_ATTRS)
231
+ if reserved_attrs is None:
232
+ reserved_attrs = RESERVED_ATTRS
233
+ elif isinstance(reserved_attrs, str):
234
+ raise TypeError("reserved_attrs must be a sequence of field names, not a string")
235
+ self.reserved_attrs = set(reserved_attrs)
227
236
  self.timestamp = timestamp
228
237
 
229
238
  self._skip_fields = set(self._required_fields)
230
239
  self._skip_fields.update(self.reserved_attrs)
231
- self.defaults = defaults if defaults is not None else {}
232
240
  self.exc_info_as_array = exc_info_as_array
233
241
  self.stack_info_as_array = stack_info_as_array
234
242
  return
235
243
 
244
+ def usesTime(self) -> bool:
245
+ """Return whether the parsed format fields include `asctime`."""
246
+ return "asctime" in self._required_fields
247
+
236
248
  def format(self, record: logging.LogRecord) -> str:
237
249
  """Formats a log record and serializes to json
238
250
 
251
+ When `record.msg` is a `dict`, adding `exc_info` and `stack_info` does not
252
+ modify the caller's dict.
253
+
239
254
  Args:
240
255
  record: the record to format
256
+
257
+ *Changed in 4.2.0*: a `dict` `record.msg` is copied instead of modified
258
+ in place.
241
259
  """
242
260
  message_dict: dict[str, Any] = {}
243
261
  # TODO: logging.LogRecord.msg and logging.LogRecord.message in typeshed
244
262
  # are always type of str. We shouldn't need to override that.
245
263
  if isinstance(record.msg, dict):
246
- message_dict = record.msg
264
+ message_dict = record.msg.copy()
247
265
  record.message = ""
248
266
  else:
249
267
  record.message = record.getMessage()
@@ -257,7 +275,9 @@ class BaseJsonFormatter(logging.Formatter):
257
275
  if record.exc_info and not message_dict.get("exc_info"):
258
276
  message_dict["exc_info"] = self.formatException(record.exc_info)
259
277
  if not message_dict.get("exc_info") and record.exc_text:
260
- message_dict["exc_info"] = record.exc_text
278
+ message_dict["exc_info"] = (
279
+ record.exc_text.splitlines() if self.exc_info_as_array else record.exc_text
280
+ )
261
281
 
262
282
  # Display formatted record of stack frames
263
283
  # default format is a string returned from :func:`traceback.print_stack`
@@ -296,20 +316,30 @@ class BaseJsonFormatter(logging.Formatter):
296
316
  raise ValueError(f"Style {self._style!r} is not supported")
297
317
 
298
318
  if isinstance(self._style, logging.StringTemplateStyle):
299
- formatter_style_pattern = STYLE_STRING_TEMPLATE_REGEX
300
-
301
- elif isinstance(self._style, logging.StrFormatStyle):
302
- formatter_style_pattern = STYLE_STRING_FORMAT_REGEX
303
-
304
- elif isinstance(self._style, logging.PercentStyle):
319
+ # String templates support both ${name} and $name, and $$ is an escaped literal
320
+ return [
321
+ match.group("braced") or match.group("named")
322
+ for match in STYLE_STRING_TEMPLATE_REGEX.finditer(self._fmt)
323
+ if match.group("braced") or match.group("named")
324
+ ]
325
+
326
+ if isinstance(self._style, logging.StrFormatStyle):
327
+ return [
328
+ field_name
329
+ for _, field_name, _, _ in string.Formatter().parse(self._fmt)
330
+ if field_name
331
+ ]
332
+
333
+ if isinstance(self._style, logging.PercentStyle):
305
334
  # PercentStyle is parent class of StringTemplateStyle and StrFormatStyle
306
335
  # 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")
336
+ return [
337
+ match.group("named")
338
+ for match in STYLE_PERCENT_REGEX.finditer(self._fmt)
339
+ if match.group("named")
340
+ ]
311
341
 
312
- return formatter_style_pattern.findall(self._fmt)
342
+ raise ValueError(f"Style {self._style!r} is not supported")
313
343
 
314
344
  def serialize_log_record(self, log_data: LogData) -> str:
315
345
  """Returns the final representation of the data to be logged
@@ -152,10 +152,18 @@ def use_exception_default(obj: Any) -> TypeGuard[BaseException]:
152
152
  def exception_default(obj: BaseException) -> str:
153
153
  """Default function for exception instances
154
154
 
155
+ Includes the exception's own notes, if present. Exception groups retain
156
+ their summary without recursively formatting their children.
157
+
155
158
  Args:
156
159
  obj: object to handle
160
+
161
+ *Changed in 4.3*: Includes exception notes if available.
157
162
  """
158
- return f"{obj.__class__.__name__}: {obj}"
163
+ result = f"{obj.__class__.__name__}: {obj}"
164
+ if hasattr(obj, "__notes__"):
165
+ return "\n".join([result, *obj.__notes__])
166
+ return result
159
167
 
160
168
 
161
169
  def use_traceback_default(obj: Any) -> TypeGuard[TracebackType]: