yapl-kit 0.2.0__tar.gz → 0.3.0__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.
Files changed (61) hide show
  1. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/PKG-INFO +147 -22
  2. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/PUBLIC.md +144 -17
  3. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/pyproject.toml +5 -6
  4. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/pyproject.toml.orig +8 -11
  5. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/__init__.py +0 -4
  6. yapl_kit-0.3.0/src/yapl/_optional.py +20 -0
  7. yapl_kit-0.3.0/src/yapl/_validation.py +21 -0
  8. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/config.py +88 -26
  9. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/capture.py +25 -1
  10. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/context.py +15 -2
  11. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/flush.py +5 -0
  12. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/models.py +20 -0
  13. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/models.py +2 -2
  14. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/plain.py +17 -4
  15. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/policy.py +5 -0
  16. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/text.py +3 -1
  17. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/__init__.py +2 -0
  18. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/_types.py +2 -0
  19. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/formatters.py +17 -2
  20. yapl_kit-0.3.0/src/yapl/extensions/formatting/policy.py +111 -0
  21. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/processors.py +24 -3
  22. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/structured.py +20 -4
  23. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/__init__.py +2 -0
  24. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/handler.py +98 -17
  25. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/models.py +32 -1
  26. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/policy.py +2 -5
  27. yapl_kit-0.3.0/src/yapl/extensions/tracing.py +30 -0
  28. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/__init__.py +1 -5
  29. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/client.py +25 -13
  30. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/handler.py +32 -2
  31. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/models.py +15 -4
  32. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/payloads.py +40 -18
  33. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/factory.py +334 -149
  34. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/filters/enrichment.py +2 -3
  35. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/filters/suppression.py +7 -7
  36. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/levels.py +5 -0
  37. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/logger.py +2 -2
  38. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/stack/callsite.py +1 -1
  39. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/stack/tracer.py +111 -14
  40. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/status.py +2 -2
  41. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/types.py +0 -6
  42. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/context.py +0 -0
  43. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/__init__.py +0 -0
  44. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/__init__.py +0 -0
  45. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/colors.py +0 -0
  46. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/registry.py +0 -0
  47. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/structured.py +0 -0
  48. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/themes.py +0 -0
  49. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/_helpers.py +0 -0
  50. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/json.py +0 -0
  51. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/logfmt.py +0 -0
  52. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/plain.py +0 -0
  53. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/text.py +0 -0
  54. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/queue.py +0 -0
  55. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/worker.py +0 -0
  56. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/filters/__init__.py +0 -0
  57. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/py.typed +0 -0
  58. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/stack/__init__.py +0 -0
  59. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/testing.py +0 -0
  60. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl_kit/__init__.py +0 -0
  61. {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl_kit/py.typed +0 -0
@@ -1,14 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: yapl-kit
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: YetAnotherPythonLogger - a minimal, extensible logging toolkit built on top of Python's standard logging module.
5
5
  Author: SirKaiMartin
6
+ Requires-Dist: opentelemetry-api>=1.0 ; extra == 'opentelemetry'
6
7
  Requires-Dist: httpx>=0.28.1 ; extra == 'webhook'
7
8
  Requires-Python: >=3.13
8
- Provides-Extra: buffering
9
- Provides-Extra: coloring
10
- Provides-Extra: formatting
11
- Provides-Extra: queueing
9
+ Provides-Extra: opentelemetry
12
10
  Provides-Extra: webhook
13
11
  Description-Content-Type: text/markdown
14
12
 
@@ -18,9 +16,11 @@ Description-Content-Type: text/markdown
18
16
 
19
17
  > A modern, minimal, and extensible logging toolkit built on top of Python's standard `logging` module.
20
18
 
21
- YAPL keeps the stdlib logging ecosystem intact while adding structured logging, queue-backed delivery, formatting, coloring, buffering, stack helpers, and optional webhook support.
19
+ YAPL keeps the stdlib logging ecosystem intact while adding structured logging, queue-backed delivery, formatting, coloring, buffering, stack helpers, and webhook support.
22
20
 
23
- The core stays dependency-free. Optional integrations are installed only when you need them.
21
+ All YAPL feature families ship in the single `yapl-kit` distribution. The base
22
+ install uses only the standard library; optional integrations install their
23
+ runtime dependencies through extras.
24
24
 
25
25
  ## Installation
26
26
 
@@ -38,17 +38,15 @@ With `pip`:
38
38
  pip install yapl-kit
39
39
  ```
40
40
 
41
- Install all optional features:
41
+ Install webhook or OpenTelemetry integrations only when needed:
42
42
 
43
43
  ```bash
44
- uv add "yapl-kit[formatting,coloring,buffering,queueing,webhook]"
44
+ pip install 'yapl-kit[webhook]'
45
+ pip install 'yapl-kit[opentelemetry]'
45
46
  ```
46
47
 
47
- or:
48
-
49
- ```bash
50
- pip install "yapl-kit[formatting,coloring,buffering,queueing,webhook]"
51
- ```
48
+ With `uv`, use `uv add 'yapl-kit[webhook]'` or
49
+ `uv add 'yapl-kit[opentelemetry]'`.
52
50
 
53
51
  The canonical import namespace is `yapl`:
54
52
 
@@ -119,7 +117,7 @@ YAPL does not replace Python logging. It builds on stdlib handlers, formatters,
119
117
 
120
118
  The main design goals are:
121
119
 
122
- - keep the core small and dependency-free
120
+ - keep the distribution small and focused
123
121
  - preserve compatibility with Python's logging ecosystem
124
122
  - keep expensive formatting and I/O off the application hot path
125
123
  - make queueing, retries, and shutdown behavior bounded
@@ -129,6 +127,7 @@ The main design goals are:
129
127
  ## Configuration
130
128
 
131
129
  YAPL supports direct setup arguments as well as typed configuration.
130
+ Install `yapl-kit[webhook]` before configuring a webhook sink.
132
131
 
133
132
  ```python
134
133
  from yapl import WebhookConfig, YaplConfig, get_logger, setup_logging
@@ -152,6 +151,54 @@ log.info("Application started")
152
151
 
153
152
  Setup arguments override the supplied `YaplConfig` for that call without mutating the configuration object.
154
153
 
154
+ ### Filtering and custom levels
155
+
156
+ Use suppression rules to drop matching records from every configured sink. All
157
+ conditions in one rule must match; a record is dropped when any rule matches.
158
+ Use an `OutputConfig` filter when a condition should apply to just that sink.
159
+ Sink filters can be standard `logging.Filter` objects or callables that accept a
160
+ `LogRecord` and return whether to keep it.
161
+
162
+ ```python
163
+ import logging
164
+
165
+ import yapl
166
+ from yapl import OutputConfig, SuppressionRules, YaplConfig
167
+
168
+ NOTICE_LEVEL = 25
169
+ yapl.register_level("NOTICE", NOTICE_LEVEL)
170
+ yapl.register_level_method("NOTICE")
171
+
172
+ config = YaplConfig(
173
+ level=logging.DEBUG,
174
+ queue_enabled=False,
175
+ sinks=(
176
+ OutputConfig(
177
+ "stdout",
178
+ filters=(lambda record: record.levelno >= NOTICE_LEVEL,),
179
+ ),
180
+ ),
181
+ suppression=SuppressionRules().drop(
182
+ logger="myapp.noisy",
183
+ logger_prefix=True,
184
+ ),
185
+ )
186
+
187
+ try:
188
+ log = yapl.setup_logging(config)
189
+ log.notice("operator_notice")
190
+ finally:
191
+ yapl.shutdown_logging()
192
+ ```
193
+
194
+ Register a custom level before using it in configuration or logging calls. The
195
+ optional `register_level_method` call adds a convenience method such as
196
+ `log.notice`; without it, use the standard `log.log(NOTICE_LEVEL, ...)` method.
197
+ The root logging level and each sink's filter are separate gates: this example
198
+ keeps DEBUG enabled globally, then applies the NOTICE threshold only to the
199
+ console sink. See [`development/demos/filters_and_levels.py`](development/demos/filters_and_levels.py)
200
+ for a runnable example with both suppression rules and sink filtering.
201
+
155
202
  ## Output Profiles
156
203
 
157
204
  A single process can use different output profiles for different sinks.
@@ -186,6 +233,86 @@ log.info("payment_processed", request_id="req-42")
186
233
 
187
234
  Use `resource` for stable source metadata such as service, environment, region, or cluster. Keep request IDs, user IDs, trace IDs, and other high-cardinality values as normal structured fields. File output profiles also support bounded rotation with `file_max_bytes` and `file_backup_count`; those options are valid only for file sinks.
188
235
 
236
+ ### Trace correlation
237
+
238
+ When an application uses OpenTelemetry, YAPL can copy the active span's IDs
239
+ onto each record so structured outputs include `trace_id` and `span_id`:
240
+
241
+ ```python
242
+ from yapl import YaplConfig, setup_logging, shutdown_logging
243
+
244
+ setup_logging(YaplConfig(capture_trace_context=True))
245
+ # Call this during application shutdown to drain queued output.
246
+ shutdown_logging()
247
+ ```
248
+
249
+ Install the `opentelemetry` extra, which supplies `opentelemetry-api`, and
250
+ configure an OpenTelemetry SDK or instrumentation package to create active
251
+ spans. YAPL reads the IDs while the log call runs, so they are retained when sink
252
+ work is queued. Existing `trace_id` or `span_id` record fields take precedence.
253
+
254
+ ### Per-sink processing
255
+
256
+ Each `OutputConfig` can add processing that applies only to that sink:
257
+
258
+ ```python
259
+ import logging
260
+
261
+ from yapl import OutputConfig, YaplConfig
262
+ from yapl.extensions.formatting import AddServiceName, RedactSecrets
263
+
264
+
265
+ class OnlyErrors(logging.Filter):
266
+ def filter(self, record: logging.LogRecord) -> bool:
267
+ return record.levelno >= logging.ERROR
268
+
269
+
270
+ config = YaplConfig(
271
+ sinks=(
272
+ OutputConfig(
273
+ "stdout",
274
+ format="text",
275
+ processors=(AddServiceName("checkout-console"),),
276
+ enrichment={"audience": "operator"},
277
+ filters=(OnlyErrors(),),
278
+ ),
279
+ OutputConfig(
280
+ "file",
281
+ format="jsonl",
282
+ file_path="checkout.jsonl",
283
+ processors=(RedactSecrets(("password", "token")),),
284
+ fields=("timestamp", "level", "message", "context"),
285
+ ),
286
+ ),
287
+ )
288
+ ```
289
+
290
+ `filters` are standard record filters and `enrichment` adds context fields on
291
+ an isolated copy of the record. `processors` transform the normalized event.
292
+ `redact_secret_fields` and `redaction_replacement` provide a concise redaction
293
+ policy, `schema` maps output names to dotted event paths, and `fields` retains
294
+ top-level event fields. A sink with a stricter redaction policy cannot leak its
295
+ unredacted record through another sink: each sink receives an isolated record
296
+ and event copy.
297
+
298
+ The deterministic order is: record filters, context enrichment, global
299
+ processors, sink processors, redaction, schema projection, field selection,
300
+ then formatting or webhook payload construction. Existing per-sink levels and
301
+ formatters still run at their normal handler boundaries. Queue workers apply
302
+ the same policy before each target handler, and webhook processing remains on
303
+ the webhook worker.
304
+
305
+ `RedactSecrets` remains available for custom processor pipelines. When using
306
+ `redact_secret_fields`, set it to an empty tuple to explicitly disable the
307
+ webhook's default redaction for that sink; omit it to retain the webhook
308
+ configuration's default policy.
309
+
310
+ For a standard-library custom formatter, set `OutputConfig.formatter`. YAPL
311
+ applies that sink's processors and event policy to an isolated record before
312
+ delegating to the formatter. The transformed normalized event is also
313
+ available as `record.yapl_event`; without sink policies, the original formatter
314
+ instance is used unchanged.
315
+
189
316
  ## Formatting
190
317
 
191
318
  Structured, text, JSON Lines, and logfmt output are available through the formatting extension.
@@ -225,12 +352,10 @@ Queue capacity limits the number of retained records, not their total byte size.
225
352
 
226
353
  ## Webhooks
227
354
 
228
- Webhook delivery is optional and uses `httpx`.
229
-
230
- Install it with:
355
+ Webhook delivery requires the optional `webhook` extra, which installs `httpx`:
231
356
 
232
357
  ```bash
233
- uv add "yapl-kit[webhook]"
358
+ pip install 'yapl-kit[webhook]'
234
359
  ```
235
360
 
236
361
  Configure a webhook:
@@ -339,9 +464,9 @@ Python cannot safely terminate arbitrary user handler code. Keep custom handlers
339
464
 
340
465
  `queue_enabled=False` runs normal stdlib handlers directly, so file, console, or custom sink work may occur on the caller thread.
341
466
 
342
- ## Optional Modules
467
+ ## Extension Modules
343
468
 
344
- YAPL keeps concrete optional features under explicit modules:
469
+ YAPL keeps feature families under explicit modules within the main distribution:
345
470
 
346
471
  ```python
347
472
  from yapl.stack import FunctionCallTracer, get_callsite
@@ -352,7 +477,7 @@ from yapl.extensions.queueing import QueueingConfig
352
477
  from yapl.extensions.webhook import WebhookConfig
353
478
  ```
354
479
 
355
- The `yapl.extensions` package is a namespace; concrete features live in its submodules.
480
+ The `yapl.extensions` package contains the concrete feature implementations.
356
481
 
357
482
  ## Package Philosophy
358
483
 
@@ -4,9 +4,11 @@
4
4
 
5
5
  > A modern, minimal, and extensible logging toolkit built on top of Python's standard `logging` module.
6
6
 
7
- YAPL keeps the stdlib logging ecosystem intact while adding structured logging, queue-backed delivery, formatting, coloring, buffering, stack helpers, and optional webhook support.
7
+ YAPL keeps the stdlib logging ecosystem intact while adding structured logging, queue-backed delivery, formatting, coloring, buffering, stack helpers, and webhook support.
8
8
 
9
- The core stays dependency-free. Optional integrations are installed only when you need them.
9
+ All YAPL feature families ship in the single `yapl-kit` distribution. The base
10
+ install uses only the standard library; optional integrations install their
11
+ runtime dependencies through extras.
10
12
 
11
13
  ## Installation
12
14
 
@@ -24,17 +26,15 @@ With `pip`:
24
26
  pip install yapl-kit
25
27
  ```
26
28
 
27
- Install all optional features:
29
+ Install webhook or OpenTelemetry integrations only when needed:
28
30
 
29
31
  ```bash
30
- uv add "yapl-kit[formatting,coloring,buffering,queueing,webhook]"
32
+ pip install 'yapl-kit[webhook]'
33
+ pip install 'yapl-kit[opentelemetry]'
31
34
  ```
32
35
 
33
- or:
34
-
35
- ```bash
36
- pip install "yapl-kit[formatting,coloring,buffering,queueing,webhook]"
37
- ```
36
+ With `uv`, use `uv add 'yapl-kit[webhook]'` or
37
+ `uv add 'yapl-kit[opentelemetry]'`.
38
38
 
39
39
  The canonical import namespace is `yapl`:
40
40
 
@@ -105,7 +105,7 @@ YAPL does not replace Python logging. It builds on stdlib handlers, formatters,
105
105
 
106
106
  The main design goals are:
107
107
 
108
- - keep the core small and dependency-free
108
+ - keep the distribution small and focused
109
109
  - preserve compatibility with Python's logging ecosystem
110
110
  - keep expensive formatting and I/O off the application hot path
111
111
  - make queueing, retries, and shutdown behavior bounded
@@ -115,6 +115,7 @@ The main design goals are:
115
115
  ## Configuration
116
116
 
117
117
  YAPL supports direct setup arguments as well as typed configuration.
118
+ Install `yapl-kit[webhook]` before configuring a webhook sink.
118
119
 
119
120
  ```python
120
121
  from yapl import WebhookConfig, YaplConfig, get_logger, setup_logging
@@ -138,6 +139,54 @@ log.info("Application started")
138
139
 
139
140
  Setup arguments override the supplied `YaplConfig` for that call without mutating the configuration object.
140
141
 
142
+ ### Filtering and custom levels
143
+
144
+ Use suppression rules to drop matching records from every configured sink. All
145
+ conditions in one rule must match; a record is dropped when any rule matches.
146
+ Use an `OutputConfig` filter when a condition should apply to just that sink.
147
+ Sink filters can be standard `logging.Filter` objects or callables that accept a
148
+ `LogRecord` and return whether to keep it.
149
+
150
+ ```python
151
+ import logging
152
+
153
+ import yapl
154
+ from yapl import OutputConfig, SuppressionRules, YaplConfig
155
+
156
+ NOTICE_LEVEL = 25
157
+ yapl.register_level("NOTICE", NOTICE_LEVEL)
158
+ yapl.register_level_method("NOTICE")
159
+
160
+ config = YaplConfig(
161
+ level=logging.DEBUG,
162
+ queue_enabled=False,
163
+ sinks=(
164
+ OutputConfig(
165
+ "stdout",
166
+ filters=(lambda record: record.levelno >= NOTICE_LEVEL,),
167
+ ),
168
+ ),
169
+ suppression=SuppressionRules().drop(
170
+ logger="myapp.noisy",
171
+ logger_prefix=True,
172
+ ),
173
+ )
174
+
175
+ try:
176
+ log = yapl.setup_logging(config)
177
+ log.notice("operator_notice")
178
+ finally:
179
+ yapl.shutdown_logging()
180
+ ```
181
+
182
+ Register a custom level before using it in configuration or logging calls. The
183
+ optional `register_level_method` call adds a convenience method such as
184
+ `log.notice`; without it, use the standard `log.log(NOTICE_LEVEL, ...)` method.
185
+ The root logging level and each sink's filter are separate gates: this example
186
+ keeps DEBUG enabled globally, then applies the NOTICE threshold only to the
187
+ console sink. See [`development/demos/filters_and_levels.py`](development/demos/filters_and_levels.py)
188
+ for a runnable example with both suppression rules and sink filtering.
189
+
141
190
  ## Output Profiles
142
191
 
143
192
  A single process can use different output profiles for different sinks.
@@ -172,6 +221,86 @@ log.info("payment_processed", request_id="req-42")
172
221
 
173
222
  Use `resource` for stable source metadata such as service, environment, region, or cluster. Keep request IDs, user IDs, trace IDs, and other high-cardinality values as normal structured fields. File output profiles also support bounded rotation with `file_max_bytes` and `file_backup_count`; those options are valid only for file sinks.
174
223
 
224
+ ### Trace correlation
225
+
226
+ When an application uses OpenTelemetry, YAPL can copy the active span's IDs
227
+ onto each record so structured outputs include `trace_id` and `span_id`:
228
+
229
+ ```python
230
+ from yapl import YaplConfig, setup_logging, shutdown_logging
231
+
232
+ setup_logging(YaplConfig(capture_trace_context=True))
233
+ # Call this during application shutdown to drain queued output.
234
+ shutdown_logging()
235
+ ```
236
+
237
+ Install the `opentelemetry` extra, which supplies `opentelemetry-api`, and
238
+ configure an OpenTelemetry SDK or instrumentation package to create active
239
+ spans. YAPL reads the IDs while the log call runs, so they are retained when sink
240
+ work is queued. Existing `trace_id` or `span_id` record fields take precedence.
241
+
242
+ ### Per-sink processing
243
+
244
+ Each `OutputConfig` can add processing that applies only to that sink:
245
+
246
+ ```python
247
+ import logging
248
+
249
+ from yapl import OutputConfig, YaplConfig
250
+ from yapl.extensions.formatting import AddServiceName, RedactSecrets
251
+
252
+
253
+ class OnlyErrors(logging.Filter):
254
+ def filter(self, record: logging.LogRecord) -> bool:
255
+ return record.levelno >= logging.ERROR
256
+
257
+
258
+ config = YaplConfig(
259
+ sinks=(
260
+ OutputConfig(
261
+ "stdout",
262
+ format="text",
263
+ processors=(AddServiceName("checkout-console"),),
264
+ enrichment={"audience": "operator"},
265
+ filters=(OnlyErrors(),),
266
+ ),
267
+ OutputConfig(
268
+ "file",
269
+ format="jsonl",
270
+ file_path="checkout.jsonl",
271
+ processors=(RedactSecrets(("password", "token")),),
272
+ fields=("timestamp", "level", "message", "context"),
273
+ ),
274
+ ),
275
+ )
276
+ ```
277
+
278
+ `filters` are standard record filters and `enrichment` adds context fields on
279
+ an isolated copy of the record. `processors` transform the normalized event.
280
+ `redact_secret_fields` and `redaction_replacement` provide a concise redaction
281
+ policy, `schema` maps output names to dotted event paths, and `fields` retains
282
+ top-level event fields. A sink with a stricter redaction policy cannot leak its
283
+ unredacted record through another sink: each sink receives an isolated record
284
+ and event copy.
285
+
286
+ The deterministic order is: record filters, context enrichment, global
287
+ processors, sink processors, redaction, schema projection, field selection,
288
+ then formatting or webhook payload construction. Existing per-sink levels and
289
+ formatters still run at their normal handler boundaries. Queue workers apply
290
+ the same policy before each target handler, and webhook processing remains on
291
+ the webhook worker.
292
+
293
+ `RedactSecrets` remains available for custom processor pipelines. When using
294
+ `redact_secret_fields`, set it to an empty tuple to explicitly disable the
295
+ webhook's default redaction for that sink; omit it to retain the webhook
296
+ configuration's default policy.
297
+
298
+ For a standard-library custom formatter, set `OutputConfig.formatter`. YAPL
299
+ applies that sink's processors and event policy to an isolated record before
300
+ delegating to the formatter. The transformed normalized event is also
301
+ available as `record.yapl_event`; without sink policies, the original formatter
302
+ instance is used unchanged.
303
+
175
304
  ## Formatting
176
305
 
177
306
  Structured, text, JSON Lines, and logfmt output are available through the formatting extension.
@@ -211,12 +340,10 @@ Queue capacity limits the number of retained records, not their total byte size.
211
340
 
212
341
  ## Webhooks
213
342
 
214
- Webhook delivery is optional and uses `httpx`.
215
-
216
- Install it with:
343
+ Webhook delivery requires the optional `webhook` extra, which installs `httpx`:
217
344
 
218
345
  ```bash
219
- uv add "yapl-kit[webhook]"
346
+ pip install 'yapl-kit[webhook]'
220
347
  ```
221
348
 
222
349
  Configure a webhook:
@@ -325,9 +452,9 @@ Python cannot safely terminate arbitrary user handler code. Keep custom handlers
325
452
 
326
453
  `queue_enabled=False` runs normal stdlib handlers directly, so file, console, or custom sink work may occur on the caller thread.
327
454
 
328
- ## Optional Modules
455
+ ## Extension Modules
329
456
 
330
- YAPL keeps concrete optional features under explicit modules:
457
+ YAPL keeps feature families under explicit modules within the main distribution:
331
458
 
332
459
  ```python
333
460
  from yapl.stack import FunctionCallTracer, get_callsite
@@ -338,7 +465,7 @@ from yapl.extensions.queueing import QueueingConfig
338
465
  from yapl.extensions.webhook import WebhookConfig
339
466
  ```
340
467
 
341
- The `yapl.extensions` package is a namespace; concrete features live in its submodules.
468
+ The `yapl.extensions` package contains the concrete feature implementations.
342
469
 
343
470
  ## Package Philosophy
344
471
 
@@ -1,20 +1,16 @@
1
1
  [project]
2
2
  name = "yapl-kit"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "YetAnotherPythonLogger - a minimal, extensible logging toolkit built on top of Python's standard logging module."
5
5
  readme = "PUBLIC.md"
6
6
  requires-python = ">=3.13"
7
- dependencies = []
8
7
 
9
8
  [[project.authors]]
10
9
  name = "SirKaiMartin"
11
10
 
12
11
  [project.optional-dependencies]
13
- formatting = []
14
- coloring = []
15
- buffering = []
16
- queueing = []
17
12
  webhook = ["httpx>=0.28.1"]
13
+ opentelemetry = ["opentelemetry-api>=1.0"]
18
14
 
19
15
  [build-system]
20
16
  requires = ["uv_build>=0.12.16,<0.13.0"]
@@ -71,6 +67,9 @@ section-order = [
71
67
  "local-folder",
72
68
  ]
73
69
 
70
+ [tool.ty.src]
71
+ include = ["src"]
72
+
74
73
  [dependency-groups]
75
74
  dev = [
76
75
  "pytest>=9.1.1",
@@ -1,13 +1,16 @@
1
1
  [project]
2
2
  name = "yapl-kit"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "YetAnotherPythonLogger - a minimal, extensible logging toolkit built on top of Python's standard logging module."
5
5
  readme = "PUBLIC.md"
6
6
  authors = [
7
7
  { name = "SirKaiMartin" }
8
8
  ]
9
9
  requires-python = ">=3.13"
10
- dependencies = []
10
+
11
+ [project.optional-dependencies]
12
+ webhook = ["httpx>=0.28.1"]
13
+ opentelemetry = ["opentelemetry-api>=1.0"]
11
14
 
12
15
  [build-system]
13
16
  requires = ["uv_build>=0.12.16,<0.13.0"]
@@ -30,15 +33,6 @@ markers = [
30
33
  "performance: caller-latency, throughput, and backpressure regression coverage",
31
34
  ]
32
35
 
33
- [project.optional-dependencies]
34
- formatting = []
35
- coloring = []
36
- buffering = []
37
- queueing = []
38
- webhook = [
39
- "httpx>=0.28.1",
40
- ]
41
-
42
36
  [tool.ruff]
43
37
  target-version = "py313"
44
38
  line-length = 100
@@ -78,3 +72,6 @@ section-order = [
78
72
  "first-party",
79
73
  "local-folder",
80
74
  ]
75
+
76
+ [tool.ty.src]
77
+ include = ["src"]
@@ -4,7 +4,6 @@ from .config import (
4
4
  ConsoleConfig,
5
5
  LoggerOverrides,
6
6
  OutputConfig,
7
- StackConfig,
8
7
  SuppressionRule,
9
8
  SuppressionRules,
10
9
  YaplConfig,
@@ -52,7 +51,6 @@ from .types import (
52
51
  OutputDestination,
53
52
  OutputFormat,
54
53
  QueueOverflowPolicy,
55
- StackMode,
56
54
  )
57
55
 
58
56
 
@@ -72,8 +70,6 @@ __all__ = [
72
70
  "QueueOverflowPolicy",
73
71
  "QueueingConfig",
74
72
  "RecordFlagPolicy",
75
- "StackConfig",
76
- "StackMode",
77
73
  "SuppressionRule",
78
74
  "SuppressionRules",
79
75
  "WebhookAuthConfig",
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import import_module
4
+ from typing import Any
5
+
6
+
7
+ def require_optional_dependency(module_name: str, *, extra: str, feature: str) -> Any:
8
+ """Translate an absent optional module into an actionable package-extra error."""
9
+ try:
10
+ return import_module(module_name)
11
+ except ModuleNotFoundError as exc:
12
+ missing_module = exc.name
13
+ if missing_module is None or not (
14
+ module_name == missing_module or module_name.startswith(f"{missing_module}.")
15
+ ):
16
+ raise
17
+ raise ImportError(
18
+ f"{feature} requires the '{extra}' extra. "
19
+ f"Install it with 'pip install yapl-kit[{extra}]'."
20
+ ) from exc
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TypeAliasType, get_args
4
+
5
+
6
+ def validate_literal(
7
+ value: object,
8
+ alias: TypeAliasType,
9
+ field_name: str,
10
+ *,
11
+ error_message: str | None = None,
12
+ ) -> None:
13
+ """Reject values outside a PEP 695 ``Literal`` type alias."""
14
+ annotation: object = alias
15
+ while isinstance(annotation, TypeAliasType):
16
+ annotation = annotation.__value__
17
+
18
+ choices = get_args(annotation)
19
+ if value not in choices:
20
+ message = error_message or f"{field_name} must be one of {choices!r}; got {value!r}"
21
+ raise ValueError(message)