Skip to content

Commit

Permalink
Use defaults argument (#26)
Browse files Browse the repository at this point in the history
closes #24 

Uses `defaults` argument to prepopulate fields on records.

### Test Plan
Unit tests

---------

Co-authored-by: Nicholas Hairs <[email protected]>
  • Loading branch information
bharel and nhairs authored Nov 10, 2024
1 parent 2f773cb commit f266f86
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- `pythonjsonlogger.[ORJSON,MSGSPEC]_AVAILABLE` no longer imports the respective package when determining availability.
- `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`.
- `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.

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

Expand Down
12 changes: 12 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ logger.info(

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.

#### Default Fields

Default fields that are added to every log record prior to any other field can be set using the `default` argument.

```python
formatter = JsonFormatter(
defaults={"environment": "dev"}
)
# ...
logger.info("this overwrites the environment field", extras={"environment": "dev"})
```

#### Static Fields

Static fields that are added to every log record can be set using the `static_fields` argument.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "python-json-logger"
version = "3.1.0"
version = "3.2.0.dev1"
description = "JSON Log Formatter for the Python Logging Package"
authors = [
{name = "Zakaria Zajac", email = "[email protected]"},
Expand Down
9 changes: 8 additions & 1 deletion src/pythonjsonlogger/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ class BaseJsonFormatter(logging.Formatter):
Must not be used directly.
*New in 3.1*
*Changed in 3.2*: `defaults` argument is no longer ignored.
"""

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

def format(self, record: logging.LogRecord) -> str:
Expand Down Expand Up @@ -310,6 +314,9 @@ def add_fields(
message_dict: dictionary that was logged instead of a message. e.g
`logger.info({"is_this_message_dict": True})`
"""
for field in self.defaults:
log_record[self._get_rename(field)] = self.defaults[field]

for field in self._required_fields:
log_record[self._get_rename(field)] = record.__dict__.get(field)

Expand Down
39 changes: 39 additions & 0 deletions tests/test_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ def test_percentage_format(env: LoggingEnvironment, class_: type[BaseJsonFormatt
return


@pytest.mark.parametrize("class_", ALL_FORMATTERS)
def test_defaults_field(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
env.set_formatter(class_(defaults={"first": 1, "second": 2}))

env.logger.info("testing defaults field", extra={"first": 1234})
log_json = env.load_json()

assert log_json["first"] == 1234
assert log_json["second"] == 2
return


@pytest.mark.parametrize("class_", ALL_FORMATTERS)
def test_rename_base_field(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
env.set_formatter(class_(rename_fields={"message": "@message"}))
Expand All @@ -186,6 +198,20 @@ def test_rename_base_field(env: LoggingEnvironment, class_: type[BaseJsonFormatt
return


@pytest.mark.parametrize("class_", ALL_FORMATTERS)
def test_rename_with_defaults(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
"""Make sure that the default fields are also renamed."""
env.set_formatter(class_(rename_fields={"custom": "@custom"}, defaults={"custom": 1234}))

msg = "testing rename with defaults"
env.logger.info(msg)
log_json = env.load_json()

assert log_json["@custom"] == 1234
assert "custom" not in log_json
return


@pytest.mark.parametrize("class_", ALL_FORMATTERS)
def test_rename_missing(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
env.set_formatter(class_(rename_fields={"missing_field": "new_field"}))
Expand Down Expand Up @@ -321,6 +347,19 @@ def test_log_dict(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
return


@pytest.mark.parametrize("class_", ALL_FORMATTERS)
def test_log_dict_defaults(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
env.set_formatter(class_(defaults={"d1": 1234, "d2": "hello"}))

msg = {"d2": "world"}
env.logger.info(msg)
log_json = env.load_json()

assert log_json["d1"] == 1234
assert log_json["d2"] == "world"
return


@pytest.mark.parametrize("class_", ALL_FORMATTERS)
def test_log_extra(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
env.set_formatter(class_())
Expand Down

0 comments on commit f266f86

Please sign in to comment.