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.
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/PKG-INFO +147 -22
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/PUBLIC.md +144 -17
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/pyproject.toml +5 -6
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/pyproject.toml.orig +8 -11
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/__init__.py +0 -4
- yapl_kit-0.3.0/src/yapl/_optional.py +20 -0
- yapl_kit-0.3.0/src/yapl/_validation.py +21 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/config.py +88 -26
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/capture.py +25 -1
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/context.py +15 -2
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/flush.py +5 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/models.py +20 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/models.py +2 -2
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/plain.py +17 -4
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/policy.py +5 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/text.py +3 -1
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/__init__.py +2 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/_types.py +2 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/formatters.py +17 -2
- yapl_kit-0.3.0/src/yapl/extensions/formatting/policy.py +111 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/processors.py +24 -3
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/structured.py +20 -4
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/__init__.py +2 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/handler.py +98 -17
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/models.py +32 -1
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/policy.py +2 -5
- yapl_kit-0.3.0/src/yapl/extensions/tracing.py +30 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/__init__.py +1 -5
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/client.py +25 -13
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/handler.py +32 -2
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/models.py +15 -4
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/payloads.py +40 -18
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/factory.py +334 -149
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/filters/enrichment.py +2 -3
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/filters/suppression.py +7 -7
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/levels.py +5 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/logger.py +2 -2
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/stack/callsite.py +1 -1
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/stack/tracer.py +111 -14
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/status.py +2 -2
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/types.py +0 -6
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/context.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/__init__.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/__init__.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/colors.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/registry.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/structured.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/themes.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/_helpers.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/json.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/logfmt.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/plain.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/text.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/queue.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/worker.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/filters/__init__.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/py.typed +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/stack/__init__.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl/testing.py +0 -0
- {yapl_kit-0.2.0 → yapl_kit-0.3.0}/src/yapl_kit/__init__.py +0 -0
- {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.
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
41
|
+
Install webhook or OpenTelemetry integrations only when needed:
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
|
|
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
|
|
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
|
|
229
|
-
|
|
230
|
-
Install it with:
|
|
355
|
+
Webhook delivery requires the optional `webhook` extra, which installs `httpx`:
|
|
231
356
|
|
|
232
357
|
```bash
|
|
233
|
-
|
|
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
|
-
##
|
|
467
|
+
## Extension Modules
|
|
343
468
|
|
|
344
|
-
YAPL keeps
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
29
|
+
Install webhook or OpenTelemetry integrations only when needed:
|
|
28
30
|
|
|
29
31
|
```bash
|
|
30
|
-
|
|
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
|
|
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
|
|
215
|
-
|
|
216
|
-
Install it with:
|
|
343
|
+
Webhook delivery requires the optional `webhook` extra, which installs `httpx`:
|
|
217
344
|
|
|
218
345
|
```bash
|
|
219
|
-
|
|
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
|
-
##
|
|
455
|
+
## Extension Modules
|
|
329
456
|
|
|
330
|
-
YAPL keeps
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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)
|