Repository navigation
feat: support configurable exclusion of JSON log fields - #129
Conversation
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
There was a problem hiding this comment.
LlamaPReview — Review skipped
This repository has used its free review capacity for today (3 reviews per UTC day). Capacity resets at 2026-10-21T00:00:00Z.
LlamaPReview is free for public repositories and funded personally, so a daily bound keeps one high-volume repository from consuming the shared budget. Nothing about this pull request was judged. You can also run LlamaPReview yourself with your own provider key — see Self-hosting.
No model-driven code review was run; this result does not assess whether the PR is ready to merge.
Users wanting to drop a field such as written_ts had to subclass a formatter. json_logging.EXCLUDED_FIELDS now names fields to omit from every JSON record: standard, web and request log formatters, custom formatter subclasses, extra entries and props. The setting defaults to an empty tuple, so output is unchanged. It is read when a formatter is constructed, like the existing COMPONENT_* settings, and invalid values (a bare string, non-string entries) raise TypeError at that point rather than during a log call. Removal is one dict pop per excluded field after the record is built, so the default path adds only an empty loop. Adds tests for default output, single and multiple exclusions, empty lists, the standard, web and request formatters, extra/props, custom formatters, construction-time semantics and invalid configuration, and documents the option in README section 2.8. Fixes #98
test_request_log_formatter_honours_exclusion replaced the whole handler list of the process-wide 'flask-request-logger' and never restored it. Running the two request-logger tests left the logger with only the test's buffer handler and parent=None, so the stdout handler that init_request_instrument() installs was gone. Restore the logger's handlers in a finally block, and snapshot and restore the logger's full state (handlers, level, parent, propagate, disabled) in a fixture used by the tests that call init_request_instrument(). Also add tests that exclusion does not mutate the LogRecord, that exception text is removed only when exc_info is excluded, and that formatters built at different times keep their own snapshots. The file passes in forward and reversed order and when each test runs alone.
An invalid json_logging.EXCLUDED_FIELDS was only detected when a formatter was constructed. By then init_flask() and the other web init functions had already set _current_framework and _request_util, so correcting the setting and calling init again failed with 'Can not call init more than once'. In init_request_instrument() the stdout handler was attached before the error, so a retry left two handlers. - Validate the setting at the start of __init() and init_request_instrument(). - Accept unicode field names on Python 2 by using the module's basestring shim. - Restore the werkzeug logger as well as the request logger in the tests. - Add subprocess regression tests for recovery after invalid configuration.
- Add an end-to-end test of the setup from issue #98: an application logger has a StreamHandler before init_flask(). The test checks that the library installs JSONLogWebFormatter on that handler with the configured exclusions, adds no handler, and keeps the X-Correlation-ID. Cases: written_ts excluded, the default, and correlation_id plus an unknown field name. - Replace the request-logger fixture with an autouse fixture that restores logging._defaultFormatter and, for every existing logger, its handlers, handler formatters, level, parent, propagation and disabled state; loggers created by a test are reset. Before this, init_*() left _defaultFormatter and the formatters of unrelated library loggers (urllib3, requests) changed. - Remove a snapshot test fully covered by test_formatters_built_at_different_times_keep_their_own_snapshots. - README 2.8: exclusions apply only to formatters that use BaseJSONFormatter.format(); the option changes the JSON schema and is not a security redaction control. - Restore a space lost in init_request_instrument() during the earlier edit.
31c5074 to
08350d7
Compare
Bump the version to 1.6.0 and collect the changelog into a 1.6.0 section covering every change merged since 1.5.1: - Added: json_logging.EXCLUDED_FIELDS (#129, fixes #98) - Fixed: FastAPI request-context propagation (#123, fixes #124); outside-request correlation IDs in Flask, Quart and Connexion (#127, fixes #125); non-web get_correlation_id() (#128, fixes #126); Sanic 24+ logging config (#133) - Documentation: FastAPI/Uvicorn startup (#130, fixes #106); Gunicorn and Flask example (#131, fixes #25) - Compatibility: default output unchanged unless EXCLUDED_FIELDS is set; the framework behaviour changes; Python 3.9+ FastAPI context path; CI coverage The previous 'other integrations unchanged' and 'no change to the JSON schema' statements are replaced, since they do not hold for the whole release. The date is added when the release is cut. No library code change.
* chore(release): prepare json-logging 1.6.0 Bump the version to 1.6.0 and collect the changelog into a 1.6.0 section covering every change merged since 1.5.1: - Added: json_logging.EXCLUDED_FIELDS (#129, fixes #98) - Fixed: FastAPI request-context propagation (#123, fixes #124); outside-request correlation IDs in Flask, Quart and Connexion (#127, fixes #125); non-web get_correlation_id() (#128, fixes #126); Sanic 24+ logging config (#133) - Documentation: FastAPI/Uvicorn startup (#130, fixes #106); Gunicorn and Flask example (#131, fixes #25) - Compatibility: default output unchanged unless EXCLUDED_FIELDS is set; the framework behaviour changes; Python 3.9+ FastAPI context path; CI coverage The previous 'other integrations unchanged' and 'no change to the JSON schema' statements are replaced, since they do not hold for the whole release. The date is added when the release is cut. No library code change. * chore(release): credit Bob T. and Udit Agrawal in 1.6.0 metadata
Final status (head
08350d7, rebased on master6835303)What it does.
json_logging.EXCLUDED_FIELDS(default()) names fields to omit from every JSON record: application, web and request logs, includingextra/propsentries. It accepts a list, tuple, set or frozenset of strings. A bare string, or a non-string entry, raisesTypeError. Fixes #98.Why filtering is done in
BaseJSONFormatter.format().LogRecorditself is never modified, so other handlers see the original record.COMPONENT_*.init_*()andinit_request_instrument()validate the setting before changing any global state, so an invalid value can be corrected and the call retried.This round:
StreamHandler(io.StringIO())beforeinit_flask(enable_json=True)andinit_request_instrument(app, ...), as in the issue. A real Flask request then logs through that handler. The test does not set a formatter itself, and checks that:JSONLogWebFormatteron the existing handler;X-Correlation-IDis preserved;msg,level,typeandwritten_atremain.["written_ts"], the default()(wherewritten_tsis kept), and["correlation_id", "no_such_field"](an unknown name is ignored, andget_correlation_id()still returns the header value).format()fails the two exclusion cases. Not installing formatters on existing handlers fails all three.logging._defaultFormatterand, for every logger, its handlers, handler formatters, level, parent, propagation and disabled state. Loggers created during a test are reset._defaultFormatterand changed the handler formatters of unrelated library loggers (urllib3,requests,charset_normalizer,concurrent.futures).tests/test_flask.py, and in forward and reversed order of the full suite.test_flask.pyhad 0 failures. The only remaining leaks come fromtests/test_flask.pyon master, which this PR doesn't touch.test_formatters_built_at_different_times_keep_their_own_snapshots.BaseJSONFormatter.format(); an independentlogging.Formattermust filter fields itself;6835303, which includes fix: prevent fabricated correlation IDs outside active requests #127, fix: safely handle correlation ID lookup in non-web mode #128 and ci: add Python 3.14 unit and framework smoke-test coverage #133. OnlyCHANGELOG.mdconflicted; all master entries are kept and the Disabling unnesesary fields in output #98 entry is added. The diff against master contains only this PR's five files. fix: prevent fabricated correlation IDs outside active requests #127's request-context guards, fix: safely handle correlation ID lookup in non-web mode #128's non-web fallback and ci: add Python 3.14 unit and framework smoke-test coverage #133's Sanicpop("format", None)are intact.init_request_instrument()in an earlier round of this PR.Results:
tests/test_excluded_fields.py; 88/88 full suite on Python 3.9–3.13; blocking flake8 0. Default JSON fields are identical to master.08350d7: 15/15 (unit tests on Python 3.9–3.14; Flask/Quart/FastAPI/Sanic smoke tests on 3.9 and 3.14; Flake8). Run: https://github.com/bobbui/json-logging-python/actions/runs/39516295453Remaining limits:
basestringvalidator) is not tested; no Python 2 interpreter was available.EXCLUDED_FIELDSis a new public setting and needs maintainer approval.PR 3 —
feat: support configurable exclusion of JSON log fields(Fixes #98)Branch
feat/98-field-exclusion, commit63d5505.Design. A module setting,
json_logging.EXCLUDED_FIELDS = (), follows the existingCOMPONENT_*pattern. Thesetting is read when a formatter is constructed, so an invalid value fails at setup rather than inside a log call. A
bare string, or a collection with non-string entries, raises
TypeError. The fields are removed inBaseJSONFormatter.format(), which every JSON formatter uses, so standard, web and request records, custom subclasses,extraandpropsare all covered.Tests.
tests/test_excluded_fields.py(14 tests). On master, 13 fail because the feature is absent; the empty-listcontrol passes. They cover the default, one and several exclusions, empty lists, the three formatters,
extra/props,custom subclasses, construction-time semantics and invalid values.
Default output. Compared with master (same record, time-varying fields removed): identical.
Local results.
format()on a non-web record: master 5.35 µs, branch 5.44 µs, one run each, within noise. Not a claimed cost.Compatibility. Default-off; no schema change. Documented in README §2.8, with a note that downstream consumers may
depend on
written_ts,correlation_idandlevel.Review update (round 2)
afbb073: fixed test-state pollution.test_request_log_formatter_honours_exclusionreplaced the requestlogger's handlers and never restored them, leaving the logger with only the test's buffer handler and
parent=None.Verified before and after: the test now restores handlers in
finally, and a fixture requested by the tests that callinit_request_instrument()restores the fulllogger state (handlers, level, parent, propagate, disabled).
LogRecordis not mutated; exception text is removed only whenexc_infois excluded; formatters builtat different times keep their own snapshots.
format()on a non-web record): master 5.90 and 5.84 us; branch 5.86 and 7.09 us(second branch run has std dev 1.56 us). With one excluded field the branch measured 6.00 and 5.75 us. Not a
measurable regression at this precision, but the noise is large; treat it as no-regression-observed.
keep their setting; exclusion changes the output schema; downstream consumers may rely on removed fields; exclusion
is not a guarantee that sensitive values cannot reach other handlers.
Review update (round 3, head
31c5074)1. Invalid
EXCLUDED_FIELDSpoisoned later initialisation. Confirmed and fixed.afbb073, in two forms.EXCLUDED_FIELDS = "written_ts"followed byinit_flask(enable_json=True):__init()set_current_frameworkand_request_utilbefore the formatter constructor raisedTypeError. After the setting was corrected,init_flask()raisedRuntimeError: Can not call init more than once.init_request_instrument(): the stdout handler was attached before the error, so a corrected retry left the request logger with 2 handlers.BaseJSONFormatter.__init__, after global state had changed.__init()andinit_request_instrument()call_validated_excluded_fields(EXCLUDED_FIELDS)before changing any state. The setting is validated even when JSON is disabled, or when a custom formatter is used, because the request logger and the Flask/Connexion werkzeug logger always get json-logging formatters. Nothing else in the singleton initialisation changed.init_flask(enable_json=True),init_flask(enable_json=False)andinit_non_web(enable_json=True);init_request_instrument(): no hooks, no handler and no disabled werkzeug are left after the failure; after the retry the request logger has exactly 1 handler, and the request record carries the supplied ID, without the excluded field.2. Python 2 string types. Fixed.
basestringshim, sounicodefield names pass on Python 2; on Python 3basestringisstr.str,unicodeandbytesvalues are rejected, as are dicts,None, ints, and non-string entries (on Python 3 that includesbytesentries). Valid list, tuple, set and frozenset values, includingu""names, are tested.3. Test isolation. Fixed.
restored_request_loggernow restoreswerkzeugas well asflask-request-logger: handlers, each handler's formatter, level, parent, propagate and disabled.werkzeugleak intest_web_formatter_honours_exclusion_and_keeps_correlation_id. After: 0 leaks in this module, with each test run alone, in forward order, and in reversed order together withtests/test_flask.py. The only remaining leaks come fromtests/test_flask.py, which is already on master and outside this PR's scope.Results: 65/65 on Python 3.9–3.13 locally; blocking flake8 0; GitHub Actions 10/10 on
31c5074. Default JSON fields are unchanged from master.Compatibility note: an invalid
EXCLUDED_FIELDSnow fails atinit_*()even with JSON disabled. Before this, it failed later, or partway through.EXCLUDED_FIELDSis new in this PR, so no existing configuration is affected.Public API:
json_logging.EXCLUDED_FIELDSis a new public setting and needs maintainer approval.Summary by CodeRabbit
New Features
Documentation
Tests