Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
19025e8
refactor: sync from template v2.1.1
Jul 1, 2026
4b4d16d
chore(release): 1.0.2-rc.1
semantic-release-bot Jul 1, 2026
dbc1ecb
chore(deps): bump actions/checkout from 6 to 7
dependabot[bot] Jul 1, 2026
7f8e780
refactor: sync from template v2.1.2
Oct 23, 2026
472db78
chore(release): 1.0.2-rc.2
semantic-release-bot Oct 23, 2026
fe8970e
chore(deps): bump virtualenv from 21.3.1 to 21.14.9
dependabot[bot] Oct 23, 2026
be1fdf6
chore(deps-dev): bump black from 26.3.1 to 26.10.1
dependabot[bot] Oct 25, 2026
aa9f8c4
chore(deps): bump filelock from 3.29.0 to 4.2.1
dependabot[bot] Oct 25, 2026
16afc2e
fix: cover uv dependencies with Dependabot
disafronov Oct 25, 2026
8325d82
chore(release): 1.0.2-rc.3
semantic-release-bot Oct 25, 2026
0c64cd7
chore(deps-dev): update uv-build requirement
dependabot[bot] Oct 25, 2026
feb5519
chore(deps-dev): bump pytest from 9.0.3 to 9.1.1
dependabot[bot] Oct 25, 2026
723bd2c
chore(deps-dev): bump flake8 from 7.3.0 to 7.4.1
dependabot[bot] Oct 25, 2026
b7fa8aa
chore(deps): bump virtualenv from 21.14.9 to 21.14.13
dependabot[bot] Oct 25, 2026
9639687
refactor: deps
disafronov Oct 25, 2026
2575870
chore(release): 1.0.2-rc.4
semantic-release-bot Oct 25, 2026
b3e2b5e
ci: remove legacy PR description workflow
disafronov Oct 25, 2026
56cac4e
refactor: sync from template v2.2.0
Oct 25, 2026
ed0d624
chore(release): 1.0.2-rc.5
semantic-release-bot Oct 25, 2026
6b4ac3b
chore: sync from template v2.2.2
Oct 25, 2026
1bc2bb4
docs: correct schema file lookup description
disafronov Oct 25, 2026
3084dce
ci: standardize .github YAML filenames to .yml
disafronov Oct 25, 2026
0419433
docs: clarify project behavior and guarantees
disafronov Oct 25, 2026
4d67688
docs: clarify project behavior and guarantees
disafronov Oct 25, 2026
c2be695
docs: clarify SchemaLogger compatibility contract
disafronov Oct 25, 2026
a36fdff
docs: fix class docstring indentation
disafronov Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ updates:
interval: "daily"
labels:
- "dependencies"
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "daily"
labels:
- "dependencies"
51 changes: 51 additions & 0 deletions .github/workflows/assign-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Assign pull request

"on":
pull_request_target:
types:
- opened
- reopened
- unassigned

permissions:
pull-requests: write

concurrency:
group: audit-${{ github.ref }}
cancel-in-progress: true

jobs:
assign:
name: Assign repository maintainer
if: ${{ github.event.pull_request.assignees[0] == null }}
runs-on: ubuntu-latest

steps:
- name: Assign PR
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
OWNER_LOGIN: ${{ github.event.repository.owner.login }}
OWNER_TYPE: ${{ github.event.repository.owner.type }}
PR_ASSIGNEE: ${{ vars.PR_ASSIGNEE }}
shell: bash
run: |
set -euo pipefail

if [[ -n "$PR_ASSIGNEE" ]]; then
assignee="$PR_ASSIGNEE"
elif [[ "$OWNER_TYPE" == "User" ]]; then
assignee="$OWNER_LOGIN"
else
echo "::error::PR_ASSIGNEE is required for organization repositories"
exit 1
fi

echo "Assigning PR #${PR_NUMBER} to ${assignee}"

gh api \
--method POST \
"repos/${GH_REPO}/issues/${PR_NUMBER}/assignees" \
-f "assignees[]=${assignee}" \
--silent
57 changes: 0 additions & 57 deletions .github/workflows/auto-pr-description.yml

This file was deleted.

41 changes: 41 additions & 0 deletions .github/workflows/force-release-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Force Release PR

"on":
pull_request:
branches:
- release

jobs:
force-release-pr:
if: github.event.pull_request.head.ref == 'main'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

steps:
- name: Checkout
uses: actions/checkout@v7

- name: Generate PR description
id: gen
uses: tanmay-pathak/generate-pull-request-description@v1.0.0
with:
pull_request_url: ${{ github.event.pull_request.url }}
api_token: ${{ secrets.GITHUB_TOKEN }}

- name: Update PR description and title
uses: actions/github-script@v9
env:
PR_BODY: ${{ steps.gen.outputs.pull_request_description }}
with:
script: |
const now = new Date().toISOString().slice(0, 16).replace('T', ' ');

await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
body: process.env.PR_BODY,
title: `Release! PR for ${now}`,
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
python-version: ["3.11", "3.12", "3.13", "3.14"]
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@v7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@v7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Install uv
uses: astral-sh/setup-uv@v7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0

Expand Down Expand Up @@ -73,7 +73,7 @@ jobs:
cancel-in-progress: false
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
token: ${{ secrets.SEMANTIC_RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## [1.0.2-rc.5](https://github.com/disafronov/python-logging-objects-with-schema/compare/v1.0.2-rc.4...v1.0.2-rc.5) (2026-10-25)

## [1.0.2-rc.4](https://github.com/disafronov/python-logging-objects-with-schema/compare/v1.0.2-rc.3...v1.0.2-rc.4) (2026-10-25)

## [1.0.2-rc.3](https://github.com/disafronov/python-logging-objects-with-schema/compare/v1.0.2-rc.2...v1.0.2-rc.3) (2026-10-25)

### Bug Fixes

* cover uv dependencies with Dependabot ([16afc2e](https://github.com/disafronov/python-logging-objects-with-schema/commit/16afc2e4bb164100ac0e7021f3e2ba3693bbf5fd))

## [1.0.2-rc.2](https://github.com/disafronov/python-logging-objects-with-schema/compare/v1.0.2-rc.1...v1.0.2-rc.2) (2026-10-23)

## [1.0.2-rc.1](https://github.com/disafronov/python-logging-objects-with-schema/compare/v1.0.1...v1.0.2-rc.1) (2026-07-01)

## [1.0.1](https://github.com/disafronov/python-logging-objects-with-schema/compare/v1.0.0...v1.0.1) (2026-05-22)

### Bug Fixes
Expand Down
32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# python-logging-objects-with-schema

This library provides a logger subclass built on top of the standard `logging`
module that strictly controls additional `extra` fields using a JSON schema.
`SchemaLogger` is a drop-in replacement for `logging.Logger` that validates
`extra` fields against a JSON schema file (`logging_objects_with_schema.json`)
in your application root directory.
`SchemaLogger` extends Python's standard `logging.Logger` with schema-driven
validation and projection of `extra` fields. It preserves the standard logging
API while invalid structured data is dropped and reported separately.

The schema is defined in `logging_objects_with_schema.json`, located by
searching upward from the current working directory to the filesystem root.

## Schema as a contract

Expand Down Expand Up @@ -32,8 +33,9 @@ systems) can rely on a consistent log structure.
where `int` is declared, and `int` is **not** accepted where `float` is
declared. There are no implicit type conversions.
- All validation problems are aggregated and logged as a single ERROR message
**after** the log record has been emitted, ensuring 100% compatibility with
standard logger behavior (no exceptions are raised).
**after** the log record has been emitted. Data-validation failures do not
raise from the logging call; valid fields are emitted and violations are
reported separately.
- Application code must only send `extra` fields that are described in the
schema and match the declared Python types. Any deviation from the schema
is considered a contract violation.
Expand All @@ -48,7 +50,8 @@ pip install logging-objects-with-schema

### Quickstart (complete working example)

First, create a schema file `logging_objects_with_schema.json` in your application root:
First, create a schema file `logging_objects_with_schema.json` in the current
working directory (or any parent directory up to the filesystem root):

```json
{
Expand Down Expand Up @@ -126,7 +129,7 @@ logger.info(
```python
from logging_objects_with_schema import SchemaLogger

# SchemaLogger is a drop-in replacement - no exception handling needed.
# SchemaLogger uses the standard logging API; data-validation failures do not raise.
# If the schema has problems, the application will be terminated after
# logging schema problems to stderr.
logging.setLoggerClass(SchemaLogger)
Expand All @@ -141,9 +144,12 @@ logger.info("processing", extra={"user_id": "not-an-int"}) # Wrong type

## Schema location and format

The schema file `logging_objects_with_schema.json` must be located in your
application root directory. The library searches upward from the current working
directory until it finds the file or reaches the filesystem root.
The schema file `logging_objects_with_schema.json` is located by searching from
the current working directory upward to the filesystem root. The first file
found wins; the location of the script or package does not matter. A file in a
parent directory is used without warning, so run the application with a fixed
working directory (for example, `WORKDIR` in a container or `WorkingDirectory`
in systemd).

**Important**: If there are any problems with the schema (missing file, broken
JSON, invalid structure, etc.), the application is terminated after logging
Expand Down Expand Up @@ -343,7 +349,7 @@ All keys are merged together - they are not replaced, only supplemented.
- Each subclass must implement the logic to pass `forbidden_keys` to its parent
if it wants to propagate keys from its own subclasses
- The `forbidden_keys` parameter is optional - if not provided, only builtin
keys are used, maintaining 100% backward compatibility
keys are used, preserving the default forbidden-key behavior
- `None` and empty `set()` are semantically equivalent for `forbidden_keys` -
both mean "no additional forbidden keys" and produce the same result

Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[build-system]
requires = [ "uv_build >= 0.9.11, <0.10.0" ]
requires = [ "uv_build>=0.9.11,<0.14.0" ]
build-backend = "uv_build"

[project]
name = "logging-objects-with-schema"
version = "1.0.1"
description = "Proxy logging wrapper that validates extra fields against a JSON schema."
version = "1.0.2rc5"
description = "Schema-aware Python logging with strict extra validation and schema-driven field projection."
readme = "README.md"
requires-python = ">=3.11"
dependencies = [ ]
Expand Down
9 changes: 5 additions & 4 deletions src/logging_objects_with_schema/schema_loader.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Schema loading and validation for logging_objects_with_schema.

The schema is expected to live in a JSON file named
``logging_objects_with_schema.json`` in the application root
directory. The schema defines which extra fields are allowed to be
emitted and which Python types they must have.
``logging_objects_with_schema.json`` in the current working directory or one
of its parent directories, up to the filesystem root; the first file found is
used. The schema defines which extra fields are allowed to be emitted and which
Python types they must have.
"""

import functools
Expand Down Expand Up @@ -313,7 +314,7 @@ def _get_schema_path() -> Path:


def _load_raw_schema(schema_path: Path) -> tuple[dict[str, Any], Path]:
"""Load raw JSON schema from the application root.
"""Load raw JSON schema from the resolved schema file.

This function attempts to read and parse the schema file. If any problems
occur (file not found, I/O errors, invalid JSON, wrong top-level type),
Expand Down
20 changes: 11 additions & 9 deletions src/logging_objects_with_schema/schema_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
This class extends the standard ``logging.Logger`` to validate and filter
user-provided ``extra`` fields according to a compiled JSON schema. When
validation problems are detected, they are logged as ERROR messages *after*
the log record has been emitted, ensuring 100% compatibility with standard
logger behavior.
the main log record has been emitted. The standard logging call API is
preserved, but ``extra`` handling intentionally differs from ``logging.Logger``.
"""

import json
Expand Down Expand Up @@ -45,12 +45,14 @@ class SchemaLogger(logging.Logger):

This class extends :class:`logging.Logger` to add validation for
``extra`` fields according to a compiled JSON schema. It is designed
to be used as a drop-in replacement via :func:`logging.setLoggerClass`.
to be installed via :func:`logging.setLoggerClass`. Logging calls retain
the standard API, while ``extra`` is schema-validated and projected.

The schema is loaded from ``logging_objects_with_schema.json`` in the
application root directory during initialization. If the schema cannot
be loaded or validated, the logger instance is not created, schema
problems are logged to stderr, and the application is terminated.
The schema is loaded from ``logging_objects_with_schema.json`` found by
searching upward from the current working directory to the filesystem root
during initialization. If the schema cannot be loaded or validated, the
logger instance is not created, schema problems are logged to stderr, and
the application is terminated.

Example:
>>> import logging
Expand Down Expand Up @@ -117,8 +119,8 @@ def _log(
This method validates and filters the ``extra`` parameter according
to the compiled schema before delegating to the parent class. If
validation problems are detected, they are logged as ERROR messages
after the main log record has been emitted, ensuring compatibility
with standard logger behavior (no exceptions are raised).
after the main log record has been emitted. Invalid runtime ``extra``
values are reported separately instead of raising validation errors.

Args:
level: Logging level.
Expand Down
Loading
Loading