yapl-kit 0.2.1__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.1/PUBLIC.md → yapl_kit-0.3.0/PKG-INFO +159 -3
- yapl_kit-0.2.1/PKG-INFO → yapl_kit-0.3.0/PUBLIC.md +147 -12
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/pyproject.toml +5 -2
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/pyproject.toml.orig +5 -4
- {yapl_kit-0.2.1 → 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.1 → yapl_kit-0.3.0}/src/yapl/config.py +85 -26
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/capture.py +6 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/flush.py +5 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/models.py +19 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/models.py +1 -1
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/plain.py +3 -1
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/policy.py +5 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/text.py +3 -1
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/__init__.py +2 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/_types.py +2 -0
- {yapl_kit-0.2.1 → 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.1 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/processors.py +18 -2
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/structured.py +20 -4
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/handler.py +10 -8
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/models.py +5 -0
- {yapl_kit-0.2.1 → 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.1 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/client.py +18 -12
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/handler.py +29 -2
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/models.py +7 -3
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/payloads.py +24 -8
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/factory.py +168 -50
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/filters/enrichment.py +2 -3
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/filters/suppression.py +6 -6
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/levels.py +5 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/logger.py +2 -2
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/stack/callsite.py +1 -1
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/types.py +0 -6
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/context.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/__init__.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/buffering/context.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/__init__.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/colors.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/registry.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/structured.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/coloring/themes.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/_helpers.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/json.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/logfmt.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/plain.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/formatting/text.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/__init__.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/queue.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/queueing/worker.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/extensions/webhook/__init__.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/filters/__init__.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/py.typed +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/stack/__init__.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/stack/tracer.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/status.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl/testing.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl_kit/__init__.py +0 -0
- {yapl_kit-0.2.1 → yapl_kit-0.3.0}/src/yapl_kit/py.typed +0 -0
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: yapl-kit
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: YetAnotherPythonLogger - a minimal, extensible logging toolkit built on top of Python's standard logging module.
|
|
5
|
+
Author: SirKaiMartin
|
|
6
|
+
Requires-Dist: opentelemetry-api>=1.0 ; extra == 'opentelemetry'
|
|
7
|
+
Requires-Dist: httpx>=0.28.1 ; extra == 'webhook'
|
|
8
|
+
Requires-Python: >=3.13
|
|
9
|
+
Provides-Extra: opentelemetry
|
|
10
|
+
Provides-Extra: webhook
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
1
13
|
# YAPL Kit
|
|
2
14
|
|
|
3
15
|
### Yet Another Python Logger
|
|
@@ -6,8 +18,9 @@
|
|
|
6
18
|
|
|
7
19
|
YAPL keeps the stdlib logging ecosystem intact while adding structured logging, queue-backed delivery, formatting, coloring, buffering, stack helpers, and webhook support.
|
|
8
20
|
|
|
9
|
-
All YAPL feature families ship in the single `yapl-kit` distribution.
|
|
10
|
-
|
|
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.
|
|
11
24
|
|
|
12
25
|
## Installation
|
|
13
26
|
|
|
@@ -25,6 +38,16 @@ With `pip`:
|
|
|
25
38
|
pip install yapl-kit
|
|
26
39
|
```
|
|
27
40
|
|
|
41
|
+
Install webhook or OpenTelemetry integrations only when needed:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install 'yapl-kit[webhook]'
|
|
45
|
+
pip install 'yapl-kit[opentelemetry]'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With `uv`, use `uv add 'yapl-kit[webhook]'` or
|
|
49
|
+
`uv add 'yapl-kit[opentelemetry]'`.
|
|
50
|
+
|
|
28
51
|
The canonical import namespace is `yapl`:
|
|
29
52
|
|
|
30
53
|
```python
|
|
@@ -104,6 +127,7 @@ The main design goals are:
|
|
|
104
127
|
## Configuration
|
|
105
128
|
|
|
106
129
|
YAPL supports direct setup arguments as well as typed configuration.
|
|
130
|
+
Install `yapl-kit[webhook]` before configuring a webhook sink.
|
|
107
131
|
|
|
108
132
|
```python
|
|
109
133
|
from yapl import WebhookConfig, YaplConfig, get_logger, setup_logging
|
|
@@ -127,6 +151,54 @@ log.info("Application started")
|
|
|
127
151
|
|
|
128
152
|
Setup arguments override the supplied `YaplConfig` for that call without mutating the configuration object.
|
|
129
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
|
+
|
|
130
202
|
## Output Profiles
|
|
131
203
|
|
|
132
204
|
A single process can use different output profiles for different sinks.
|
|
@@ -161,6 +233,86 @@ log.info("payment_processed", request_id="req-42")
|
|
|
161
233
|
|
|
162
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.
|
|
163
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
|
+
|
|
164
316
|
## Formatting
|
|
165
317
|
|
|
166
318
|
Structured, text, JSON Lines, and logfmt output are available through the formatting extension.
|
|
@@ -200,7 +352,11 @@ Queue capacity limits the number of retained records, not their total byte size.
|
|
|
200
352
|
|
|
201
353
|
## Webhooks
|
|
202
354
|
|
|
203
|
-
Webhook delivery
|
|
355
|
+
Webhook delivery requires the optional `webhook` extra, which installs `httpx`:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
pip install 'yapl-kit[webhook]'
|
|
359
|
+
```
|
|
204
360
|
|
|
205
361
|
Configure a webhook:
|
|
206
362
|
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: yapl-kit
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Summary: YetAnotherPythonLogger - a minimal, extensible logging toolkit built on top of Python's standard logging module.
|
|
5
|
-
Author: SirKaiMartin
|
|
6
|
-
Requires-Dist: httpx>=0.28.1
|
|
7
|
-
Requires-Python: >=3.13
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
|
|
10
1
|
# YAPL Kit
|
|
11
2
|
|
|
12
3
|
### Yet Another Python Logger
|
|
@@ -15,8 +6,9 @@ Description-Content-Type: text/markdown
|
|
|
15
6
|
|
|
16
7
|
YAPL keeps the stdlib logging ecosystem intact while adding structured logging, queue-backed delivery, formatting, coloring, buffering, stack helpers, and webhook support.
|
|
17
8
|
|
|
18
|
-
All YAPL feature families ship in the single `yapl-kit` distribution.
|
|
19
|
-
|
|
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.
|
|
20
12
|
|
|
21
13
|
## Installation
|
|
22
14
|
|
|
@@ -34,6 +26,16 @@ With `pip`:
|
|
|
34
26
|
pip install yapl-kit
|
|
35
27
|
```
|
|
36
28
|
|
|
29
|
+
Install webhook or OpenTelemetry integrations only when needed:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install 'yapl-kit[webhook]'
|
|
33
|
+
pip install 'yapl-kit[opentelemetry]'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
With `uv`, use `uv add 'yapl-kit[webhook]'` or
|
|
37
|
+
`uv add 'yapl-kit[opentelemetry]'`.
|
|
38
|
+
|
|
37
39
|
The canonical import namespace is `yapl`:
|
|
38
40
|
|
|
39
41
|
```python
|
|
@@ -113,6 +115,7 @@ The main design goals are:
|
|
|
113
115
|
## Configuration
|
|
114
116
|
|
|
115
117
|
YAPL supports direct setup arguments as well as typed configuration.
|
|
118
|
+
Install `yapl-kit[webhook]` before configuring a webhook sink.
|
|
116
119
|
|
|
117
120
|
```python
|
|
118
121
|
from yapl import WebhookConfig, YaplConfig, get_logger, setup_logging
|
|
@@ -136,6 +139,54 @@ log.info("Application started")
|
|
|
136
139
|
|
|
137
140
|
Setup arguments override the supplied `YaplConfig` for that call without mutating the configuration object.
|
|
138
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
|
+
|
|
139
190
|
## Output Profiles
|
|
140
191
|
|
|
141
192
|
A single process can use different output profiles for different sinks.
|
|
@@ -170,6 +221,86 @@ log.info("payment_processed", request_id="req-42")
|
|
|
170
221
|
|
|
171
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.
|
|
172
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
|
+
|
|
173
304
|
## Formatting
|
|
174
305
|
|
|
175
306
|
Structured, text, JSON Lines, and logfmt output are available through the formatting extension.
|
|
@@ -209,7 +340,11 @@ Queue capacity limits the number of retained records, not their total byte size.
|
|
|
209
340
|
|
|
210
341
|
## Webhooks
|
|
211
342
|
|
|
212
|
-
Webhook delivery
|
|
343
|
+
Webhook delivery requires the optional `webhook` extra, which installs `httpx`:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
pip install 'yapl-kit[webhook]'
|
|
347
|
+
```
|
|
213
348
|
|
|
214
349
|
Configure a webhook:
|
|
215
350
|
|
|
@@ -1,14 +1,17 @@
|
|
|
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 = ["httpx>=0.28.1"]
|
|
8
7
|
|
|
9
8
|
[[project.authors]]
|
|
10
9
|
name = "SirKaiMartin"
|
|
11
10
|
|
|
11
|
+
[project.optional-dependencies]
|
|
12
|
+
webhook = ["httpx>=0.28.1"]
|
|
13
|
+
opentelemetry = ["opentelemetry-api>=1.0"]
|
|
14
|
+
|
|
12
15
|
[build-system]
|
|
13
16
|
requires = ["uv_build>=0.12.16,<0.13.0"]
|
|
14
17
|
build-backend = "uv_build"
|
|
@@ -1,15 +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
|
-
|
|
11
|
-
|
|
12
|
-
]
|
|
10
|
+
|
|
11
|
+
[project.optional-dependencies]
|
|
12
|
+
webhook = ["httpx>=0.28.1"]
|
|
13
|
+
opentelemetry = ["opentelemetry-api>=1.0"]
|
|
13
14
|
|
|
14
15
|
[build-system]
|
|
15
16
|
requires = ["uv_build>=0.12.16,<0.13.0"]
|
|
@@ -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)
|