Skip to content

Commit f266f86

Browse files
bharelnhairs
andauthored
Use defaults argument (#26)
closes #24 Uses `defaults` argument to prepopulate fields on records. ### Test Plan Unit tests --------- Co-authored-by: Nicholas Hairs <info@nicholashairs.com>
1 parent 2f773cb commit f266f86

File tree

5 files changed

+61
-2
lines changed

5 files changed

+61
-2
lines changed

docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Changed
1010
- `pythonjsonlogger.[ORJSON,MSGSPEC]_AVAILABLE` no longer imports the respective package when determining availability.
1111
- `pythonjsonlogger.[orjson,msgspec]` now throws a `pythonjsonlogger.exception.MissingPackageError` when required libraries are not available. These contain more information about what is missing whilst still being an `ImportError`.
12+
- `defaults` parameter is no longer ignored and now conforms to the standard library. Setting a defaults dictionary will add the specified keys if the those keys do not exist in a record or weren't passed by the `extra` parameter when logging a message.
1213

1314
## [3.1.0](https://github.com/nhairs/python-json-logger/compare/v3.0.1...v3.1.0) - 2023-05-28
1415

docs/quickstart.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ logger.info(
7878

7979
Finally, any non-standard attributes added to a `LogRecord` will also be included in the logged data. See [Cookbook: Request / Trace IDs](cookbook.md#request-trace-ids) for an example.
8080

81+
#### Default Fields
82+
83+
Default fields that are added to every log record prior to any other field can be set using the `default` argument.
84+
85+
```python
86+
formatter = JsonFormatter(
87+
defaults={"environment": "dev"}
88+
)
89+
# ...
90+
logger.info("this overwrites the environment field", extras={"environment": "dev"})
91+
```
92+
8193
#### Static Fields
8294

8395
Static fields that are added to every log record can be set using the `static_fields` argument.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "python-json-logger"
7-
version = "3.1.0"
7+
version = "3.2.0.dev1"
88
description = "JSON Log Formatter for the Python Logging Package"
99
authors = [
1010
{name = "Zakaria Zajac", email = "zak@madzak.com"},

src/pythonjsonlogger/core.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ class BaseJsonFormatter(logging.Formatter):
132132
Must not be used directly.
133133
134134
*New in 3.1*
135+
136+
*Changed in 3.2*: `defaults` argument is no longer ignored.
135137
"""
136138

137139
_style: Union[logging.PercentStyle, str] # type: ignore[assignment]
@@ -161,7 +163,8 @@ def __init__(
161163
style: how to extract log fields from `fmt`
162164
validate: validate `fmt` against style, if implementing a custom `style` you
163165
must set this to `False`.
164-
defaults: ignored - kept for compatibility with python 3.10+
166+
defaults: a dictionary containing default fields that are added before all other fields and
167+
may be overridden. The supplied fields are still subject to `rename_fields`.
165168
prefix: an optional string prefix added at the beginning of
166169
the formatted string
167170
rename_fields: an optional dict, used to rename field names in the output.
@@ -215,6 +218,7 @@ def __init__(
215218
self._required_fields = self.parse()
216219
self._skip_fields = set(self._required_fields)
217220
self._skip_fields.update(self.reserved_attrs)
221+
self.defaults = defaults if defaults is not None else {}
218222
return
219223

220224
def format(self, record: logging.LogRecord) -> str:
@@ -310,6 +314,9 @@ def add_fields(
310314
message_dict: dictionary that was logged instead of a message. e.g
311315
`logger.info({"is_this_message_dict": True})`
312316
"""
317+
for field in self.defaults:
318+
log_record[self._get_rename(field)] = self.defaults[field]
319+
313320
for field in self._required_fields:
314321
log_record[self._get_rename(field)] = record.__dict__.get(field)
315322

tests/test_formatters.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,18 @@ def test_percentage_format(env: LoggingEnvironment, class_: type[BaseJsonFormatt
174174
return
175175

176176

177+
@pytest.mark.parametrize("class_", ALL_FORMATTERS)
178+
def test_defaults_field(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
179+
env.set_formatter(class_(defaults={"first": 1, "second": 2}))
180+
181+
env.logger.info("testing defaults field", extra={"first": 1234})
182+
log_json = env.load_json()
183+
184+
assert log_json["first"] == 1234
185+
assert log_json["second"] == 2
186+
return
187+
188+
177189
@pytest.mark.parametrize("class_", ALL_FORMATTERS)
178190
def test_rename_base_field(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
179191
env.set_formatter(class_(rename_fields={"message": "@message"}))
@@ -186,6 +198,20 @@ def test_rename_base_field(env: LoggingEnvironment, class_: type[BaseJsonFormatt
186198
return
187199

188200

201+
@pytest.mark.parametrize("class_", ALL_FORMATTERS)
202+
def test_rename_with_defaults(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
203+
"""Make sure that the default fields are also renamed."""
204+
env.set_formatter(class_(rename_fields={"custom": "@custom"}, defaults={"custom": 1234}))
205+
206+
msg = "testing rename with defaults"
207+
env.logger.info(msg)
208+
log_json = env.load_json()
209+
210+
assert log_json["@custom"] == 1234
211+
assert "custom" not in log_json
212+
return
213+
214+
189215
@pytest.mark.parametrize("class_", ALL_FORMATTERS)
190216
def test_rename_missing(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
191217
env.set_formatter(class_(rename_fields={"missing_field": "new_field"}))
@@ -321,6 +347,19 @@ def test_log_dict(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
321347
return
322348

323349

350+
@pytest.mark.parametrize("class_", ALL_FORMATTERS)
351+
def test_log_dict_defaults(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
352+
env.set_formatter(class_(defaults={"d1": 1234, "d2": "hello"}))
353+
354+
msg = {"d2": "world"}
355+
env.logger.info(msg)
356+
log_json = env.load_json()
357+
358+
assert log_json["d1"] == 1234
359+
assert log_json["d2"] == "world"
360+
return
361+
362+
324363
@pytest.mark.parametrize("class_", ALL_FORMATTERS)
325364
def test_log_extra(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
326365
env.set_formatter(class_())

0 commit comments

Comments
 (0)