logging-objects-with-schema 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logging-objects-with-schema
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Proxy logging wrapper that validates extra fields against a JSON schema.
5
5
  Keywords: logging,schema,validation,structured-logging,structured-logs
6
6
  Author: Dmitrii Safronov
@@ -57,7 +57,7 @@ to appear in logs and which types they must have.
57
57
 
58
58
  - Application code must only send `extra` fields that are described in the schema
59
59
  and match the declared Python types. Any deviation (unknown fields, wrong types,
60
- `None` values, disallowed list elements) is reported via `DataValidationError`
60
+ `None` values, disallowed list elements) is logged as an ERROR message
61
61
  *after* the log record has been emitted.
62
62
  - The schema file (`logging_objects_with_schema.json`) is a shared, versioned
63
63
  artifact that defines the shape of structured log payloads for all downstream
@@ -72,9 +72,9 @@ to appear in logs and which types they must have.
72
72
  - Any mismatch between runtime values and the declared types is also treated as
73
73
  a data error.
74
74
  - All validation problems (unknown fields, wrong types, disallowed list
75
- elements, `None` values, etc.) are aggregated into a single
76
- `DataValidationError` that is raised **after** the log record has been
77
- emitted.
75
+ elements, `None` values, etc.) are aggregated and logged as a single
76
+ ERROR message **after** the log record has been emitted, ensuring 100%
77
+ compatibility with standard logger behavior (no exceptions are raised).
78
78
  - The schema is treated as the only source of truth for which `extra` fields
79
79
  are allowed to appear in logs. Any deviation from the schema is considered a
80
80
  contract violation between the producer of `extra` and the schema author.
@@ -165,27 +165,19 @@ logger.info(
165
165
  ### Error handling example
166
166
 
167
167
  ```python
168
- from logging_objects_with_schema import SchemaLogger, DataValidationError, SchemaValidationError
169
-
170
- try:
171
- # This will raise SchemaValidationError if schema file is missing or invalid
172
- logging.setLoggerClass(SchemaLogger)
173
- logger = logging.getLogger("service")
174
- except SchemaValidationError as e:
175
- print(f"Schema validation failed: {e}")
176
- for problem in e.problems:
177
- print(f" - {problem.message}")
178
- # Decide whether to abort or continue with default logger
179
- raise
180
-
181
- # When logging, handle DataValidationError
182
- try:
183
- logger.info("processing", extra={"user_id": "not-an-int"}) # Wrong type
184
- except DataValidationError as e:
185
- print(f"Data validation failed: {e}")
186
- for problem in e.problems:
187
- print(f" - {problem.message}")
188
- # Note: the valid part of the log was already emitted before the exception
168
+ from logging_objects_with_schema import SchemaLogger
169
+
170
+ # SchemaLogger is a drop-in replacement - no exception handling needed.
171
+ # If the schema has problems, the application will be terminated after
172
+ # logging schema problems to stderr.
173
+ logging.setLoggerClass(SchemaLogger)
174
+ logger = logging.getLogger("service")
175
+
176
+ # When logging with invalid data, validation errors are automatically
177
+ # logged as ERROR messages. No exception handling is needed.
178
+ logger.info("processing", extra={"user_id": "not-an-int"}) # Wrong type
179
+ # The valid part of the log is emitted, and validation errors are logged
180
+ # as ERROR messages with details about the problems.
189
181
  ```
190
182
 
191
183
  ### API compatibility with ``logging.Logger``
@@ -207,12 +199,11 @@ except DataValidationError as e:
207
199
  - Schema tree depth is limited to a maximum nesting level (currently 100). Any
208
200
  branch that exceeds this depth is ignored and reported as a schema problem.
209
201
 
210
- If the schema file is not found, or cannot be read/parsed/validated, a
211
- `SchemaValidationError("Schema has problems", problems=[...])` is raised when
212
- a `SchemaLogger` instance is created. In this case the logger instance is not
213
- created at all. The `problems` list contains a detailed description of the
214
- issue, including the path where the file was expected (based on the current
215
- working directory at schema discovery time).
202
+ If the schema file is not found, or cannot be read/parsed/validated, the
203
+ logger instance is not created, schema problems are logged to stderr, and
204
+ the application is terminated via `os._exit(1)`. The error message contains
205
+ a detailed description of all issues, including the path where the file was
206
+ expected (based on the current working directory at schema discovery time).
216
207
 
217
208
  An example schema:
218
209
 
@@ -233,8 +224,21 @@ An example schema:
233
224
  }
234
225
  ```
235
226
 
236
- - An inner node is an object without `type` and `source`.
237
- - A leaf node is an object with both `type` and `source`.
227
+ An example of a valid empty schema (no leaves, no problems):
228
+
229
+ ```json
230
+ {}
231
+ ```
232
+
233
+ An empty schema is valid and does not cause errors. When using an empty schema,
234
+ no `extra` fields will be included in log records, and any attempt to log with
235
+ `extra` fields will result in validation errors being logged as ERROR messages.
236
+
237
+ - An inner node is an object without `type` and `source` (neither field is present).
238
+ - A leaf node is an object that has at least one of `type` or `source` fields.
239
+ However, a valid leaf node must have both `type` and `source` fields. If a
240
+ leaf node is missing either field or has an empty value, it will be reported
241
+ as a schema problem during validation.
238
242
  - `type` is one of the allowed Python type names: `"str"`, `"int"`, `"float"`,
239
243
  `"bool"`, or `"list"`.
240
244
  - For `"list"` type, an additional `item_type` field is required to declare
@@ -296,7 +300,8 @@ message similar to:
296
300
 
297
301
  > Field 'tags' is a list but contains elements with types [...]; expected all elements to be of type str
298
302
 
299
- and a `DataValidationError` is raised **after** the log record has been emitted.
303
+ and an ERROR message is logged **after** the log record has been emitted with
304
+ the format: `"Log data does not match schema: {problem1}; {problem2}; ..."`.
300
305
 
301
306
  ### Multiple leaves with the same source
302
307
 
@@ -310,7 +315,7 @@ When a `source` is referenced by multiple leaves:
310
315
  - The value is written only to those leaf locations where the runtime type
311
316
  matches the expected type.
312
317
  - For leaf locations where the type does not match, a `DataProblem` is added
313
- to the `DataValidationError` that is raised after logging.
318
+ to the ERROR message that is logged after logging.
314
319
 
315
320
  Example schema with duplicate source usage:
316
321
 
@@ -346,15 +351,23 @@ consistent type expectations when reusing a `source` field.
346
351
  - If there are **any** problems with the schema (missing file, broken JSON,
347
352
  invalid `type` values, conflicting root fields that match system logging
348
353
  fields, malformed structure, etc.):
349
- - a `SchemaValidationError("Schema has problems", problems=[...])` is raised;
350
- - the logger instance is not created.
354
+ - the logger instance is not created (schema validation happens before
355
+ the logger is initialized);
356
+ - schema problems are logged to stderr in the format:
357
+ `"Schema has problems: {problem1}; {problem2}; ..."`;
358
+ - the application is terminated via `os._exit(1)`.
351
359
  - If there are no problems:
352
360
  - the schema is compiled into a `CompiledSchema`;
353
361
  - the logger is created and starts using this schema to validate `extra`
354
362
  fields.
363
+ - A valid empty schema (e.g., `{}` or a schema with only inner nodes and no
364
+ leaves) is treated as valid and does not cause errors. The logger is created
365
+ successfully, but no `extra` fields will be included in log records.
355
366
 
356
- The application decides whether `SchemaValidationError` is a fatal error that
357
- should abort startup, or whether it should be logged and ignored.
367
+ **Note**: System-level errors (OSError, ValueError, RuntimeError) that occur
368
+ during schema compilation are converted to `SchemaProblem` instances and
369
+ handled the same way as schema validation problems - the application is
370
+ terminated after logging the error to stderr.
358
371
 
359
372
  ## Schema caching and thread safety
360
373
 
@@ -406,8 +419,9 @@ should abort startup, or whether it should be logged and ignored.
406
419
  **it is simply not included in the final log record**.
407
420
 
408
421
  In all of these cases a `DataProblem` is recorded for each offending field, and
409
- if at least one problem is present a single `DataValidationError` is raised
410
- **after** the log record has been emitted.
422
+ if at least one problem is present, a single ERROR message is logged
423
+ **after** the log record has been emitted. The error message format is:
424
+ `"Log data does not match schema: {problem1}; {problem2}; ..."`.
411
425
 
412
426
  - When a `source` is used in multiple leaves (see "Multiple leaves with the same
413
427
  source" above), the value is validated and written independently for each leaf
@@ -423,18 +437,13 @@ High-level algorithm inside `SchemaLogger`:
423
437
  2. A new structured payload is built from the schema and the given `extra`.
424
438
  3. Only this structured payload is passed to the underlying stdlib logger.
425
439
  4. After logging, if any validation problems were detected, a single
426
- `DataValidationError` is raised with the full `problems` list.
427
-
428
- ## Exceptions
429
-
430
- - **`SchemaValidationError`**:
431
- - Any problem with the schema (missing file, broken JSON, invalid types,
432
- conflicting root fields, malformed structure, etc.).
433
- - Exposes a `problems` list describing each violation.
434
- - **`DataValidationError`**:
435
- - Any problem with a specific `extra` payload during logging:
436
- - the runtime type does not match the expected type;
437
- - a list contains non-primitive elements (only str, int, float, bool allowed);
438
- - the field is redundant and not described in the schema.
439
- - Raised **after** the valid part of the payload has been logged.
440
- - Contains a `problems` list describing the offending fields.
440
+ ERROR message is logged with the format:
441
+ `"Log data does not match schema: {problem1}; {problem2}; ..."`
442
+ (no exception is raised, ensuring 100% compatibility with standard logger behavior).
443
+
444
+ ## Error handling
445
+
446
+ - Schema problems are handled internally: errors are logged to stderr and
447
+ the application is terminated via `os._exit(1)`.
448
+ - No exceptions are raised by `SchemaLogger` during initialization, making
449
+ it a true drop-in replacement for `logging.Logger`.
@@ -24,7 +24,7 @@ to appear in logs and which types they must have.
24
24
 
25
25
  - Application code must only send `extra` fields that are described in the schema
26
26
  and match the declared Python types. Any deviation (unknown fields, wrong types,
27
- `None` values, disallowed list elements) is reported via `DataValidationError`
27
+ `None` values, disallowed list elements) is logged as an ERROR message
28
28
  *after* the log record has been emitted.
29
29
  - The schema file (`logging_objects_with_schema.json`) is a shared, versioned
30
30
  artifact that defines the shape of structured log payloads for all downstream
@@ -39,9 +39,9 @@ to appear in logs and which types they must have.
39
39
  - Any mismatch between runtime values and the declared types is also treated as
40
40
  a data error.
41
41
  - All validation problems (unknown fields, wrong types, disallowed list
42
- elements, `None` values, etc.) are aggregated into a single
43
- `DataValidationError` that is raised **after** the log record has been
44
- emitted.
42
+ elements, `None` values, etc.) are aggregated and logged as a single
43
+ ERROR message **after** the log record has been emitted, ensuring 100%
44
+ compatibility with standard logger behavior (no exceptions are raised).
45
45
  - The schema is treated as the only source of truth for which `extra` fields
46
46
  are allowed to appear in logs. Any deviation from the schema is considered a
47
47
  contract violation between the producer of `extra` and the schema author.
@@ -132,27 +132,19 @@ logger.info(
132
132
  ### Error handling example
133
133
 
134
134
  ```python
135
- from logging_objects_with_schema import SchemaLogger, DataValidationError, SchemaValidationError
136
-
137
- try:
138
- # This will raise SchemaValidationError if schema file is missing or invalid
139
- logging.setLoggerClass(SchemaLogger)
140
- logger = logging.getLogger("service")
141
- except SchemaValidationError as e:
142
- print(f"Schema validation failed: {e}")
143
- for problem in e.problems:
144
- print(f" - {problem.message}")
145
- # Decide whether to abort or continue with default logger
146
- raise
147
-
148
- # When logging, handle DataValidationError
149
- try:
150
- logger.info("processing", extra={"user_id": "not-an-int"}) # Wrong type
151
- except DataValidationError as e:
152
- print(f"Data validation failed: {e}")
153
- for problem in e.problems:
154
- print(f" - {problem.message}")
155
- # Note: the valid part of the log was already emitted before the exception
135
+ from logging_objects_with_schema import SchemaLogger
136
+
137
+ # SchemaLogger is a drop-in replacement - no exception handling needed.
138
+ # If the schema has problems, the application will be terminated after
139
+ # logging schema problems to stderr.
140
+ logging.setLoggerClass(SchemaLogger)
141
+ logger = logging.getLogger("service")
142
+
143
+ # When logging with invalid data, validation errors are automatically
144
+ # logged as ERROR messages. No exception handling is needed.
145
+ logger.info("processing", extra={"user_id": "not-an-int"}) # Wrong type
146
+ # The valid part of the log is emitted, and validation errors are logged
147
+ # as ERROR messages with details about the problems.
156
148
  ```
157
149
 
158
150
  ### API compatibility with ``logging.Logger``
@@ -174,12 +166,11 @@ except DataValidationError as e:
174
166
  - Schema tree depth is limited to a maximum nesting level (currently 100). Any
175
167
  branch that exceeds this depth is ignored and reported as a schema problem.
176
168
 
177
- If the schema file is not found, or cannot be read/parsed/validated, a
178
- `SchemaValidationError("Schema has problems", problems=[...])` is raised when
179
- a `SchemaLogger` instance is created. In this case the logger instance is not
180
- created at all. The `problems` list contains a detailed description of the
181
- issue, including the path where the file was expected (based on the current
182
- working directory at schema discovery time).
169
+ If the schema file is not found, or cannot be read/parsed/validated, the
170
+ logger instance is not created, schema problems are logged to stderr, and
171
+ the application is terminated via `os._exit(1)`. The error message contains
172
+ a detailed description of all issues, including the path where the file was
173
+ expected (based on the current working directory at schema discovery time).
183
174
 
184
175
  An example schema:
185
176
 
@@ -200,8 +191,21 @@ An example schema:
200
191
  }
201
192
  ```
202
193
 
203
- - An inner node is an object without `type` and `source`.
204
- - A leaf node is an object with both `type` and `source`.
194
+ An example of a valid empty schema (no leaves, no problems):
195
+
196
+ ```json
197
+ {}
198
+ ```
199
+
200
+ An empty schema is valid and does not cause errors. When using an empty schema,
201
+ no `extra` fields will be included in log records, and any attempt to log with
202
+ `extra` fields will result in validation errors being logged as ERROR messages.
203
+
204
+ - An inner node is an object without `type` and `source` (neither field is present).
205
+ - A leaf node is an object that has at least one of `type` or `source` fields.
206
+ However, a valid leaf node must have both `type` and `source` fields. If a
207
+ leaf node is missing either field or has an empty value, it will be reported
208
+ as a schema problem during validation.
205
209
  - `type` is one of the allowed Python type names: `"str"`, `"int"`, `"float"`,
206
210
  `"bool"`, or `"list"`.
207
211
  - For `"list"` type, an additional `item_type` field is required to declare
@@ -263,7 +267,8 @@ message similar to:
263
267
 
264
268
  > Field 'tags' is a list but contains elements with types [...]; expected all elements to be of type str
265
269
 
266
- and a `DataValidationError` is raised **after** the log record has been emitted.
270
+ and an ERROR message is logged **after** the log record has been emitted with
271
+ the format: `"Log data does not match schema: {problem1}; {problem2}; ..."`.
267
272
 
268
273
  ### Multiple leaves with the same source
269
274
 
@@ -277,7 +282,7 @@ When a `source` is referenced by multiple leaves:
277
282
  - The value is written only to those leaf locations where the runtime type
278
283
  matches the expected type.
279
284
  - For leaf locations where the type does not match, a `DataProblem` is added
280
- to the `DataValidationError` that is raised after logging.
285
+ to the ERROR message that is logged after logging.
281
286
 
282
287
  Example schema with duplicate source usage:
283
288
 
@@ -313,15 +318,23 @@ consistent type expectations when reusing a `source` field.
313
318
  - If there are **any** problems with the schema (missing file, broken JSON,
314
319
  invalid `type` values, conflicting root fields that match system logging
315
320
  fields, malformed structure, etc.):
316
- - a `SchemaValidationError("Schema has problems", problems=[...])` is raised;
317
- - the logger instance is not created.
321
+ - the logger instance is not created (schema validation happens before
322
+ the logger is initialized);
323
+ - schema problems are logged to stderr in the format:
324
+ `"Schema has problems: {problem1}; {problem2}; ..."`;
325
+ - the application is terminated via `os._exit(1)`.
318
326
  - If there are no problems:
319
327
  - the schema is compiled into a `CompiledSchema`;
320
328
  - the logger is created and starts using this schema to validate `extra`
321
329
  fields.
330
+ - A valid empty schema (e.g., `{}` or a schema with only inner nodes and no
331
+ leaves) is treated as valid and does not cause errors. The logger is created
332
+ successfully, but no `extra` fields will be included in log records.
322
333
 
323
- The application decides whether `SchemaValidationError` is a fatal error that
324
- should abort startup, or whether it should be logged and ignored.
334
+ **Note**: System-level errors (OSError, ValueError, RuntimeError) that occur
335
+ during schema compilation are converted to `SchemaProblem` instances and
336
+ handled the same way as schema validation problems - the application is
337
+ terminated after logging the error to stderr.
325
338
 
326
339
  ## Schema caching and thread safety
327
340
 
@@ -373,8 +386,9 @@ should abort startup, or whether it should be logged and ignored.
373
386
  **it is simply not included in the final log record**.
374
387
 
375
388
  In all of these cases a `DataProblem` is recorded for each offending field, and
376
- if at least one problem is present a single `DataValidationError` is raised
377
- **after** the log record has been emitted.
389
+ if at least one problem is present, a single ERROR message is logged
390
+ **after** the log record has been emitted. The error message format is:
391
+ `"Log data does not match schema: {problem1}; {problem2}; ..."`.
378
392
 
379
393
  - When a `source` is used in multiple leaves (see "Multiple leaves with the same
380
394
  source" above), the value is validated and written independently for each leaf
@@ -390,18 +404,13 @@ High-level algorithm inside `SchemaLogger`:
390
404
  2. A new structured payload is built from the schema and the given `extra`.
391
405
  3. Only this structured payload is passed to the underlying stdlib logger.
392
406
  4. After logging, if any validation problems were detected, a single
393
- `DataValidationError` is raised with the full `problems` list.
394
-
395
- ## Exceptions
396
-
397
- - **`SchemaValidationError`**:
398
- - Any problem with the schema (missing file, broken JSON, invalid types,
399
- conflicting root fields, malformed structure, etc.).
400
- - Exposes a `problems` list describing each violation.
401
- - **`DataValidationError`**:
402
- - Any problem with a specific `extra` payload during logging:
403
- - the runtime type does not match the expected type;
404
- - a list contains non-primitive elements (only str, int, float, bool allowed);
405
- - the field is redundant and not described in the schema.
406
- - Raised **after** the valid part of the payload has been logged.
407
- - Contains a `problems` list describing the offending fields.
407
+ ERROR message is logged with the format:
408
+ `"Log data does not match schema: {problem1}; {problem2}; ..."`
409
+ (no exception is raised, ensuring 100% compatibility with standard logger behavior).
410
+
411
+ ## Error handling
412
+
413
+ - Schema problems are handled internally: errors are logged to stderr and
414
+ the application is terminated via `os._exit(1)`.
415
+ - No exceptions are raised by `SchemaLogger` during initialization, making
416
+ it a true drop-in replacement for `logging.Logger`.
@@ -0,0 +1,97 @@
1
+ [build-system]
2
+ requires = [ "uv_build >= 0.9.11, <0.10.0" ]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "logging-objects-with-schema"
7
+ version = "0.1.2"
8
+ description = "Proxy logging wrapper that validates extra fields against a JSON schema."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [ ]
12
+ license = "Apache-2.0"
13
+ license-files = [ "LICENSE" ]
14
+ keywords = [
15
+ "logging",
16
+ "schema",
17
+ "validation",
18
+ "structured-logging",
19
+ "structured-logs"
20
+ ]
21
+ classifiers = [
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
+ "Programming Language :: Python :: 3 :: Only",
29
+ "Intended Audience :: Developers",
30
+ "Topic :: System :: Logging"
31
+ ]
32
+
33
+ [[project.authors]]
34
+ name = "Dmitrii Safronov"
35
+ email = "dhameoelin+logging-objects-with-schema@gmail.com"
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=7.0.0",
40
+ "pytest-cov>=4.0.0",
41
+ "black>=23.0.0",
42
+ "isort>=5.12.0",
43
+ "flake8>=6.0.0",
44
+ "flake8-pyproject>=1.2.3",
45
+ "mypy>=1.18.1",
46
+ "bandit>=1.8.6",
47
+ "pre-commit>=3.5.0"
48
+ ]
49
+
50
+ [project.urls]
51
+ Source = "https://github.com/disafronov/python-logging-objects-with-schema"
52
+ Issues = "https://github.com/disafronov/python-logging-objects-with-schema/issues"
53
+
54
+ [tool.isort]
55
+ profile = "black"
56
+ line_length = 88
57
+ multi_line_output = 3
58
+
59
+ [tool.black]
60
+ line-length = 88
61
+ include = "\\.pyi?$"
62
+ extend-exclude = """
63
+ /(
64
+ \\.eggs
65
+ | \\.git
66
+ | \\.hg
67
+ | \\.mypy_cache
68
+ | \\.tox
69
+ | \\.venv
70
+ | build
71
+ | dist
72
+ )/
73
+ """
74
+
75
+ [tool.flake8]
76
+ max-line-length = 88
77
+ extend-ignore = [ "E203", "W503" ]
78
+ exclude = [ ".venv", "__pycache__", ".git", "htmlcov" ]
79
+
80
+ [tool.mypy]
81
+ exclude = "tests/"
82
+ warn_return_any = true
83
+ warn_unused_configs = true
84
+ disallow_untyped_defs = true
85
+ disallow_incomplete_defs = true
86
+ check_untyped_defs = true
87
+ disallow_untyped_decorators = true
88
+ no_implicit_optional = true
89
+ warn_redundant_casts = true
90
+ warn_unused_ignores = true
91
+ warn_no_return = true
92
+ warn_unreachable = true
93
+ strict_equality = true
94
+
95
+ [tool.bandit]
96
+ skips = [ "B101", "B601" ]
97
+ exclude_dirs = [ ".venv", "__pycache__", ".git", "htmlcov" ]
@@ -7,11 +7,8 @@ according to an application-defined JSON schema.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from .errors import DataValidationError, SchemaValidationError
11
10
  from .schema_logger import SchemaLogger
12
11
 
13
12
  __all__ = [
14
13
  "SchemaLogger",
15
- "SchemaValidationError",
16
- "DataValidationError",
17
14
  ]
@@ -0,0 +1,27 @@
1
+ """Custom exception types used by logging_objects_with_schema."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass
9
+ class SchemaProblem:
10
+ """Describes a single problem encountered while loading the schema.
11
+
12
+ Attributes:
13
+ message: Human-readable description of the problem.
14
+ """
15
+
16
+ message: str
17
+
18
+
19
+ @dataclass
20
+ class DataProblem:
21
+ """Describes a single problem encountered while validating log data.
22
+
23
+ Attributes:
24
+ message: Human-readable description of the validation problem.
25
+ """
26
+
27
+ message: str