Proxy logging wrapper that validates extra fields against a JSON schema.
Project description
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.
Schema as a contract
The JSON schema is treated as a contract between all parties that produce
and consume logs in the system. The schema file (logging_objects_with_schema.json)
is a shared, versioned artifact that defines which structured fields are allowed
to appear in logs and which Python types they must have. This contract ensures
that all downstream consumers (search systems, alerts, dashboards, external
systems) can rely on a consistent log structure.
Strictness guarantees:
extrafields never go directly into logs. They are always projected through the schema: values fromextraare taken bysourcefield names and placed into the log structure according to the schema paths. Only fields explicitly described in the schema (as leaves withtypeandsource) can ever reach your logs. The schema is the only source of truth for whichextrafields are allowed.- Any
extrafield that is not described in the schema is treated as a data error: it is dropped from the log output and recorded as a validation problem. - Any mismatch between runtime values and the declared types (wrong types,
Nonevalues, disallowed list elements) is also treated as a data error. - 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).
- Application code must only send
extrafields that are described in the schema and match the declared Python types. Any deviation from the schema is considered a contract violation.
Installation
pip install logging-objects-with-schema
Basic usage
Quickstart (complete working example)
First, create a schema file logging_objects_with_schema.json in your application root:
{
"ServicePayload": {
"RequestID": {"type": "str", "source": "request_id"},
"UserID": {"type": "int", "source": "user_id"}
}
}
Then, set up and use the logger:
import logging
import sys
from logging_objects_with_schema import SchemaLogger
# Set SchemaLogger as the default logger class
logging.setLoggerClass(SchemaLogger)
# Configure handlers and formatters as usual
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("%(message)s %(ServicePayload)s"))
# Get loggers using standard logging API
logger = logging.getLogger("service")
logger.setLevel(logging.INFO)
logger.addHandler(handler)
# Use the logger - extra fields are validated against the schema
logger.info("request processed", extra={"request_id": "abc-123", "user_id": 42})
Example with nested structures
Schema with nested structure:
{
"ServicePayload": {
"RequestID": {"type": "str", "source": "request_id"},
"UserID": {"type": "int", "source": "user_id"},
"Metrics": {
"CPU": {"type": "float", "source": "cpu_usage"},
"Memory": {"type": "float", "source": "memory_usage"},
"Network": {
"In": {"type": "int", "source": "network_in"},
"Out": {"type": "int", "source": "network_out"}
}
}
}
}
Usage:
logger.info(
"metrics collected",
extra={
"request_id": "req-123",
"user_id": 42,
"cpu_usage": 75.5,
"memory_usage": 60.2,
"network_in": 1024,
"network_out": 2048,
}
)
Error handling example
from logging_objects_with_schema import SchemaLogger
# SchemaLogger is a drop-in replacement - no exception handling needed.
# If the schema has problems, the application will be terminated after
# logging schema problems to stderr.
logging.setLoggerClass(SchemaLogger)
logger = logging.getLogger("service")
# When logging with invalid data, validation errors are automatically
# logged as ERROR messages. No exception handling is needed.
logger.info("processing", extra={"user_id": "not-an-int"}) # Wrong type
# The valid part of the log is emitted, and validation errors are logged
# as ERROR messages in JSON format: {"validation_errors": [{"field": "...", "error": "...", "value": "..."}]}
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.
Important: If there are any problems with the schema (missing file, broken JSON, invalid structure, etc.), the application is terminated after logging schema problems to stderr. Schema validation happens when the first logger instance is created.
Note: The schema is compiled once per process and cached. Schema changes require an application restart to take effect. The library is thread-safe.
A valid empty schema ({}) is allowed and will result in no extra fields
being included in log records.
An example schema:
{
"ServicePayload": {
"RequestID": {"type": "str", "source": "request_id"},
"UserID": {"type": "int", "source": "user_id"},
"Metrics": {
"CPU": {"type": "float", "source": "cpu_usage"},
"Memory": {"type": "float", "source": "memory_usage"},
"Network": {
"In": {"type": "int", "source": "network_in"},
"Out": {"type": "int", "source": "network_out"}
}
}
}
}
An example of a valid empty schema (no leaves, no problems):
{}
Schema structure:
- Inner nodes: Objects without
typeandsourcefields (used for nesting). - Leaf nodes: Objects with both
typeandsourcefields. A valid leaf must have both fields present and non-empty. type: One of"str","int","float","bool", or"list".source: The name of the field inextrafrom which the value is taken.- Root key restrictions: Root keys cannot conflict with standard
loggingmodule fields (e.g.,name,levelno,pathname). Such conflicts cause schema validation to fail.
List-typed fields:
For "type": "list", you must also specify "item_type" (one of "str",
"int", "float", "bool"). Lists must contain homogeneous primitive elements
of the declared type. Empty lists are allowed; nested lists and dictionaries are
not permitted.
Example of a valid list field:
{
"ServicePayload": {
"Tags": {
"type": "list",
"source": "tags",
"item_type": "str"
}
}
}
Usage:
logger.info(
"request processed",
extra={
"tags": ["blue", "fast", "cached"], # list[str] – valid
},
)
Invalid example (non-primitive elements are rejected):
logger.info("request processed", extra={"tags": [{"key": "color"}]}) # Invalid
# Validation error is logged as ERROR after the log record is emitted
Multiple leaves with the same source:
A single source field name can be used in multiple leaves. The value is
validated independently for each leaf and written to all matching locations.
If types conflict, the value is written only where the type matches, and
validation errors are reported for mismatched locations.
Example:
{
"ServicePayload": {
"RequestID": {"type": "str", "source": "request_id"},
"Metadata": {
"ID": {"type": "str", "source": "request_id"}
}
}
}
With extra={"request_id": "abc-123"}, the value appears in both
ServicePayload.RequestID and ServicePayload.Metadata.ID.
Inheritance and custom forbidden root keys
SchemaLogger supports inheritance, allowing subclasses to add additional
forbidden root keys for schema validation. This is useful when you need to
prevent certain root keys from being used in your schema beyond the builtin
logging.LogRecord attributes.
Basic inheritance
Each subclass can pass the forbidden_keys parameter to the parent's
__init__() method. The builtin set of forbidden keys (standard logging.LogRecord
attributes) is always present and cannot be replaced - additional keys are
merged with the builtin set.
Example:
from logging_objects_with_schema import SchemaLogger
import logging
class MyLogger(SchemaLogger):
def __init__(self, name: str, level: int = logging.NOTSET) -> None:
# Add custom forbidden keys
super().__init__(name, level, forbidden_keys={"custom_forbidden_key"})
Multi-level inheritance
When creating a hierarchy of loggers, each subclass can pass forbidden_keys
from its own subclasses to the parent, merging them with its own set. The
library does not automatically propagate keys up the inheritance chain - each
subclass must implement this logic itself.
Example:
from logging_objects_with_schema import SchemaLogger
import logging
class ParentLogger(SchemaLogger):
def __init__(
self, name: str, level: int = logging.NOTSET, forbidden_keys: set[str] | None = None
) -> None:
# Merge parent's keys with keys from child
parent_keys = {"parent_forbidden_key"}
if forbidden_keys:
parent_keys = parent_keys | forbidden_keys
super().__init__(name, level, forbidden_keys=parent_keys)
class ChildLogger(ParentLogger):
def __init__(self, name: str, level: int = logging.NOTSET) -> None:
# Pass child's keys to parent, which will merge them
super().__init__(name, level, forbidden_keys={"child_forbidden_key"})
In this example, the final set of forbidden keys will be:
- Builtin
logging.LogRecordattributes (always present) parent_forbidden_key(fromParentLogger)child_forbidden_key(fromChildLogger)
All keys are merged together - they are not replaced, only supplemented.
Important notes
- The builtin set of forbidden keys (standard
logging.LogRecordattributes) is always present and cannot be replaced or removed - Additional forbidden keys are merged with the builtin set, not replaced
- Each subclass must implement the logic to pass
forbidden_keysto its parent if it wants to propagate keys from its own subclasses - The
forbidden_keysparameter is optional - if not provided, only builtin keys are used, maintaining 100% backward compatibility Noneand emptyset()are semantically equivalent forforbidden_keys- both mean "no additional forbidden keys" and produce the same result
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file logging_objects_with_schema-0.2.0.tar.gz.
File metadata
- Download URL: logging_objects_with_schema-0.2.0.tar.gz
- Upload date:
- Size: 26.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc7cb04490a1472c8019e2aa55e1a272f6cc7f4da42e1e4fd70eb11678c336c4
|
|
| MD5 |
161633643531d576e8f5bddb68b7bf4d
|
|
| BLAKE2b-256 |
32cf7eb2cec7e9e2428329c478096433d5d91e38a49c7d180d49574d47dfad57
|
Provenance
The following attestation bundles were made for logging_objects_with_schema-0.2.0.tar.gz:
Publisher:
publish-stable.yaml on disafronov/python-logging-objects-with-schema
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
logging_objects_with_schema-0.2.0.tar.gz -
Subject digest:
bc7cb04490a1472c8019e2aa55e1a272f6cc7f4da42e1e4fd70eb11678c336c4 - Sigstore transparency entry: 752852223
- Sigstore integration time:
-
Permalink:
disafronov/python-logging-objects-with-schema@a0d9d8446d956d7632e8b39de604232f35d84321 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/disafronov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-stable.yaml@a0d9d8446d956d7632e8b39de604232f35d84321 -
Trigger Event:
push
-
Statement type:
File details
Details for the file logging_objects_with_schema-0.2.0-py3-none-any.whl.
File metadata
- Download URL: logging_objects_with_schema-0.2.0-py3-none-any.whl
- Upload date:
- Size: 30.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
267ced925e5ce06284d8262d6413545c87ad744b273aeb26211477826833937f
|
|
| MD5 |
d5e7e21c7a99e98beac65eafcd6370a0
|
|
| BLAKE2b-256 |
6f7137b20fb83f5fe20cadf93de2ceef36f5d160c521c8b783fb2e8681c58333
|
Provenance
The following attestation bundles were made for logging_objects_with_schema-0.2.0-py3-none-any.whl:
Publisher:
publish-stable.yaml on disafronov/python-logging-objects-with-schema
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
logging_objects_with_schema-0.2.0-py3-none-any.whl -
Subject digest:
267ced925e5ce06284d8262d6413545c87ad744b273aeb26211477826833937f - Sigstore transparency entry: 752852225
- Sigstore integration time:
-
Permalink:
disafronov/python-logging-objects-with-schema@a0d9d8446d956d7632e8b39de604232f35d84321 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/disafronov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-stable.yaml@a0d9d8446d956d7632e8b39de604232f35d84321 -
Trigger Event:
push
-
Statement type: