structguru 1.2.3__tar.gz → 1.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.
- {structguru-1.2.3 → structguru-1.3.0}/Cargo.lock +9 -9
- {structguru-1.2.3 → structguru-1.3.0}/Cargo.toml +1 -1
- {structguru-1.2.3 → structguru-1.3.0}/PKG-INFO +126 -27
- {structguru-1.2.3 → structguru-1.3.0}/README.md +125 -26
- {structguru-1.2.3 → structguru-1.3.0}/crates/structguru-core/src/filter.rs +103 -30
- {structguru-1.2.3 → structguru-1.3.0}/crates/structguru-core/src/lib.rs +54 -2
- {structguru-1.2.3 → structguru-1.3.0}/crates/structguru-core/src/render.rs +98 -22
- {structguru-1.2.3 → structguru-1.3.0}/crates/structguru-core/src/worker.rs +438 -35
- structguru-1.3.0/crates/structguru-py/src/dispatch.rs +1067 -0
- {structguru-1.2.3 → structguru-1.3.0}/crates/structguru-py/src/lib.rs +693 -102
- {structguru-1.2.3 → structguru-1.3.0}/pyproject.toml +7 -4
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/__init__.py +9 -1
- structguru-1.3.0/src/structguru/_levels.py +60 -0
- structguru-1.3.0/src/structguru/_native_dispatch.py +73 -0
- structguru-1.3.0/src/structguru/_native_env.py +62 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/_runtime.py +481 -301
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/config.py +11 -6
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/core.py +248 -74
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/exceptions.py +64 -19
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/_util.py +19 -9
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/asgi.py +23 -11
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/celery.py +31 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/django.py +37 -28
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/sentry.py +17 -3
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/stdlib.py +31 -15
- structguru-1.3.0/src/structguru/settings.py +232 -0
- structguru-1.2.3/src/structguru/_native_dispatch.py +0 -264
- structguru-1.2.3/src/structguru/_native_env.py +0 -26
- {structguru-1.2.3 → structguru-1.3.0}/LICENSE +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/crates/structguru-core/Cargo.toml +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/crates/structguru-core/src/clock.rs +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/crates/structguru-core/src/value.rs +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/crates/structguru-py/Cargo.toml +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/_contextvars.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/__init__.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/_stdlib_env.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/flask.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/grpc.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/httpx.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/requests.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/integrations/sqlalchemy.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/metrics.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/otel.py +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/py.typed +0 -0
- {structguru-1.2.3 → structguru-1.3.0}/src/structguru/redaction.py +0 -0
|
@@ -34,9 +34,9 @@ checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
|
|
34
34
|
|
|
35
35
|
[[package]]
|
|
36
36
|
name = "fancy-regex"
|
|
37
|
-
version = "0.19.
|
|
37
|
+
version = "0.19.2"
|
|
38
38
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
39
|
-
checksum = "
|
|
39
|
+
checksum = "d301f5bf187b3c295fce6468d3875037a0bccc5f6b151c63cac2f85babf21912"
|
|
40
40
|
dependencies = [
|
|
41
41
|
"bit-set",
|
|
42
42
|
"regex-automata",
|
|
@@ -227,7 +227,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|
|
227
227
|
dependencies = [
|
|
228
228
|
"proc-macro2",
|
|
229
229
|
"quote",
|
|
230
|
-
"syn 3.0.
|
|
230
|
+
"syn 3.0.6",
|
|
231
231
|
]
|
|
232
232
|
|
|
233
233
|
[[package]]
|
|
@@ -245,7 +245,7 @@ dependencies = [
|
|
|
245
245
|
|
|
246
246
|
[[package]]
|
|
247
247
|
name = "structguru-core"
|
|
248
|
-
version = "1.
|
|
248
|
+
version = "1.3.0"
|
|
249
249
|
dependencies = [
|
|
250
250
|
"fancy-regex",
|
|
251
251
|
"fastrand",
|
|
@@ -257,7 +257,7 @@ dependencies = [
|
|
|
257
257
|
|
|
258
258
|
[[package]]
|
|
259
259
|
name = "structguru-py"
|
|
260
|
-
version = "1.
|
|
260
|
+
version = "1.3.0"
|
|
261
261
|
dependencies = [
|
|
262
262
|
"pyo3",
|
|
263
263
|
"regex",
|
|
@@ -277,9 +277,9 @@ dependencies = [
|
|
|
277
277
|
|
|
278
278
|
[[package]]
|
|
279
279
|
name = "syn"
|
|
280
|
-
version = "3.0.
|
|
280
|
+
version = "3.0.6"
|
|
281
281
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
282
|
-
checksum = "
|
|
282
|
+
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
|
283
283
|
dependencies = [
|
|
284
284
|
"proc-macro2",
|
|
285
285
|
"quote",
|
|
@@ -324,9 +324,9 @@ dependencies = [
|
|
|
324
324
|
|
|
325
325
|
[[package]]
|
|
326
326
|
name = "unicode-ident"
|
|
327
|
-
version = "1.0.
|
|
327
|
+
version = "1.0.26"
|
|
328
328
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
329
|
-
checksum = "
|
|
329
|
+
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
|
330
330
|
|
|
331
331
|
[[package]]
|
|
332
332
|
name = "zmij"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structguru
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -98,7 +98,7 @@ Available extras: `otel`, `celery`, `flask`, `django`, `sqlalchemy`, `grpc`,
|
|
|
98
98
|
## Quick start
|
|
99
99
|
|
|
100
100
|
```python
|
|
101
|
-
from structguru import configure, logger
|
|
101
|
+
from structguru import Logger, configure, logger
|
|
102
102
|
|
|
103
103
|
# Configure once at startup
|
|
104
104
|
configure(service="myapp", level="DEBUG", format="json")
|
|
@@ -106,8 +106,66 @@ configure(service="myapp", level="DEBUG", format="json")
|
|
|
106
106
|
# Use anywhere
|
|
107
107
|
logger.info("Hello {name}", name="world")
|
|
108
108
|
# → {"logger":"...","level":"INFO","severity":6,"timestamp":"...","service":"myapp","message":"Hello world"}
|
|
109
|
+
|
|
110
|
+
# Or choose an explicit module name
|
|
111
|
+
log = Logger(name=__name__)
|
|
109
112
|
```
|
|
110
113
|
|
|
114
|
+
## Configuration
|
|
115
|
+
|
|
116
|
+
Call `configure()` once at startup. Each call replaces the previous configuration:
|
|
117
|
+
explicit keywords override environment values, which override built-in defaults.
|
|
118
|
+
Use `update()` to retain existing options while changing selected ones:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from structguru import Settings, configure, get_config, update
|
|
122
|
+
|
|
123
|
+
configure(service="checkout", sensitive_patterns=[r"token=\w+"])
|
|
124
|
+
update(otel=True, structured_exceptions=True) # retains service and redaction
|
|
125
|
+
current = get_config() # Settings, or None after shutdown()
|
|
126
|
+
|
|
127
|
+
# Applications can load a mapping themselves; no files are read automatically.
|
|
128
|
+
settings = Settings.from_mapping({"service": "checkout", "level": "DEBUG"})
|
|
129
|
+
configure(settings) # uses this object instead of environment values
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`Settings.from_env()` resolves environment values without applying them; pass a mapping
|
|
133
|
+
instead of using the process environment when testing. `Settings` validates Python
|
|
134
|
+
values and freezes collections. Native regex compilation and file access are checked
|
|
135
|
+
when configuring; failure leaves the previous runtime active. Explicit keywords,
|
|
136
|
+
including `None` and built-in default values, win over the selected base.
|
|
137
|
+
|
|
138
|
+
`update()` never rereads the environment and requires an active runtime. An empty update
|
|
139
|
+
does nothing. Level-only updates and `set_level()` preserve queues and rate-limit state;
|
|
140
|
+
other updates rebuild writers and reset filter state. Snapshots describe configured
|
|
141
|
+
options, not buffered records, counters, stdlib bridge state, or `logger.add()` sinks.
|
|
142
|
+
Those registered sinks survive reconfiguration. Streams and callbacks retain their identity.
|
|
143
|
+
|
|
144
|
+
Levels accept case-insensitive names (including `NOTSET`) or non-negative integer
|
|
145
|
+
thresholds. Unknown names, booleans and negative integers raise `ValueError`.
|
|
146
|
+
|
|
147
|
+
### Environment configuration
|
|
148
|
+
|
|
149
|
+
| Variable | Default | Compatibility fallback |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `STRUCTGURU_SERVICE` | `app` | — |
|
|
152
|
+
| `STRUCTGURU_LEVEL` | `INFO` | `LOG_LEVEL` |
|
|
153
|
+
| `STRUCTGURU_TARGET` | `stdout` | `STRUCTGURU_NATIVE_TARGET` |
|
|
154
|
+
| `STRUCTGURU_FORMAT` | `json` | — |
|
|
155
|
+
| `STRUCTGURU_SAMPLE_RATE` | `1.0` | `STRUCTGURU_NATIVE_SAMPLE_RATE` |
|
|
156
|
+
| `STRUCTGURU_RATE_LIMIT` | disabled; period defaults to 60 seconds | `STRUCTGURU_NATIVE_RATE_LIMIT` |
|
|
157
|
+
| `STRUCTGURU_AUTOCONFIGURE` | enabled | inverse of `STRUCTGURU_LEGACY` |
|
|
158
|
+
|
|
159
|
+
New names win over their fallbacks; old names remain supported without warnings.
|
|
160
|
+
Rate limits use `MAX` or `MAX/PERIOD`, with integer counts and seconds for the period.
|
|
161
|
+
Autoconfiguration accepts `1/0`, `true/false`, `yes/no`, or `on/off` and controls import
|
|
162
|
+
only. Set it to `0` before import to configure explicitly later. Invalid selected values
|
|
163
|
+
fail validation; an invalid import-time value must be corrected or autoconfiguration
|
|
164
|
+
disabled before the application can call `configure()`.
|
|
165
|
+
|
|
166
|
+
File output, redaction, exceptions and other settings currently use Python configuration.
|
|
167
|
+
The stdlib bridge retains its separate `STRUCTGURU_STDLIB_*` options and explicit installer.
|
|
168
|
+
|
|
111
169
|
## Usage
|
|
112
170
|
|
|
113
171
|
### Log levels
|
|
@@ -191,6 +249,16 @@ that path. Install the [stdlib bridge](#stdlib-bridge) to receive them rendered
|
|
|
191
249
|
and redacted instead — while it is installed the raw delivery is suspended, so a
|
|
192
250
|
sink never sees the same record twice.
|
|
193
251
|
|
|
252
|
+
Logs emitted inside a sink callback, whether it received a native record or a
|
|
253
|
+
raw stdlib one, reach the native writer but skip callable and `logger.add()`
|
|
254
|
+
sinks, preventing recursive delivery and worker deadlocks. Outside callbacks,
|
|
255
|
+
`logger.remove()` waits for producers that already selected the removed sink
|
|
256
|
+
and for raw stdlib deliveries in progress. Lifecycle calls inside a callback,
|
|
257
|
+
including a stdlib handler's `emit()` on the raw path, cannot wait for the
|
|
258
|
+
worker; previously selected deliveries finish as that worker drains.
|
|
259
|
+
For native file/stdout mirroring, `writer_metrics()["sink_errors"]` includes
|
|
260
|
+
failed destinations even when another destination successfully writes the record.
|
|
261
|
+
|
|
194
262
|
Native delivery uses the bounded callable queue and is drained on
|
|
195
263
|
reconfiguration, `shutdown()`, fork, and interpreter exit. Call
|
|
196
264
|
`structguru.flush()` when you need to block until buffered records have actually
|
|
@@ -210,11 +278,11 @@ structguru.flush() # returns once the line has reached its sink
|
|
|
210
278
|
|
|
211
279
|
```python
|
|
212
280
|
# JSON (production)
|
|
213
|
-
|
|
281
|
+
update(service="myapp", format="json")
|
|
214
282
|
# → {"logger":"...","level":"INFO","severity":6,"timestamp":"...","service":"myapp","message":"..."}
|
|
215
283
|
|
|
216
284
|
# Console (development) — colored, human-readable
|
|
217
|
-
|
|
285
|
+
update(service="myapp", format="console")
|
|
218
286
|
# → 2026-01-15T12:00:00.123456Z [INFO ] Hello world
|
|
219
287
|
```
|
|
220
288
|
|
|
@@ -225,9 +293,9 @@ configure(service="myapp", format="console")
|
|
|
225
293
|
Mask sensitive fields automatically:
|
|
226
294
|
|
|
227
295
|
```python
|
|
228
|
-
from structguru import
|
|
296
|
+
from structguru import update
|
|
229
297
|
|
|
230
|
-
|
|
298
|
+
update(
|
|
231
299
|
sensitive_keys=["password", "token", "ssn"],
|
|
232
300
|
sensitive_patterns=[r"\b\d{3}-\d{2}-\d{4}\b"],
|
|
233
301
|
pattern_replacement="***",
|
|
@@ -235,7 +303,7 @@ configure(
|
|
|
235
303
|
```
|
|
236
304
|
|
|
237
305
|
Patterns run on Rust's linear-time regex engine (no ReDoS), which rejects
|
|
238
|
-
look-around and backreferences at `
|
|
306
|
+
look-around and backreferences at `update()` time. Most look-behinds rewrite
|
|
239
307
|
as capture groups — `(?<=password=)\S+` becomes `(password=)\S+` with
|
|
240
308
|
`pattern_replacement="$1[REDACTED]"`, so the prefix is re-emitted and the
|
|
241
309
|
secret is replaced (`password=hunter2` → `password=[REDACTED]`). Put the
|
|
@@ -248,7 +316,7 @@ backtrack limit, it is redacted entirely (fail-closed) rather than emitted
|
|
|
248
316
|
unchecked.
|
|
249
317
|
|
|
250
318
|
```python
|
|
251
|
-
|
|
319
|
+
update(
|
|
252
320
|
sensitive_patterns=[r"(?<=password=)\S+"],
|
|
253
321
|
allow_backtracking_patterns=True,
|
|
254
322
|
)
|
|
@@ -259,9 +327,9 @@ configure(
|
|
|
259
327
|
Suppress noisy logs:
|
|
260
328
|
|
|
261
329
|
```python
|
|
262
|
-
from structguru import
|
|
330
|
+
from structguru import update
|
|
263
331
|
|
|
264
|
-
|
|
332
|
+
update(sample_rate=0.1, rate_limit_max=5, rate_limit_period=60)
|
|
265
333
|
```
|
|
266
334
|
|
|
267
335
|
### Metric extraction
|
|
@@ -269,13 +337,13 @@ configure(sample_rate=0.1, rate_limit_max=5, rate_limit_period=60)
|
|
|
269
337
|
Derive metrics from log events:
|
|
270
338
|
|
|
271
339
|
```python
|
|
272
|
-
from structguru import MetricProcessor,
|
|
340
|
+
from structguru import MetricProcessor, update
|
|
273
341
|
|
|
274
342
|
metrics = MetricProcessor()
|
|
275
343
|
metrics.counter("user.login", lambda ed: login_counter.inc())
|
|
276
344
|
metrics.histogram("db.query", "duration_ms", lambda v, ed: query_hist.observe(v))
|
|
277
345
|
|
|
278
|
-
|
|
346
|
+
update(metric_processor=metrics)
|
|
279
347
|
```
|
|
280
348
|
|
|
281
349
|
### Exception formatting
|
|
@@ -283,35 +351,47 @@ configure(metric_processor=metrics)
|
|
|
283
351
|
Render exceptions as JSON-serializable dictionaries:
|
|
284
352
|
|
|
285
353
|
```python
|
|
286
|
-
from structguru import
|
|
354
|
+
from structguru import update
|
|
287
355
|
|
|
288
|
-
|
|
356
|
+
update(structured_exceptions=True, exception_max_frames=20)
|
|
289
357
|
```
|
|
290
358
|
|
|
291
359
|
`exception_max_frames=0` omits traceback frames entirely. Negative frame and
|
|
292
360
|
local-representation limits are rejected during configuration.
|
|
293
361
|
|
|
362
|
+
Formatted tracebacks (the default) carry CPython's per-frame position markers,
|
|
363
|
+
the `~~~^^^` lines, and on Python 3.11+ computing them is most of the cost of
|
|
364
|
+
`logger.exception()`. `exception_carets=False` omits them, which formats a
|
|
365
|
+
traceback about five times faster and matches what CPython prints under
|
|
366
|
+
`PYTHONNODEBUGRANGES=1`:
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
update(exception_carets=False)
|
|
370
|
+
```
|
|
371
|
+
|
|
294
372
|
### OpenTelemetry correlation
|
|
295
373
|
|
|
296
374
|
Inject trace context into every log event:
|
|
297
375
|
|
|
298
376
|
```python
|
|
299
|
-
from structguru import
|
|
377
|
+
from structguru import update
|
|
300
378
|
|
|
301
|
-
|
|
379
|
+
update(otel=True) # no-op injection when opentelemetry-api is absent
|
|
302
380
|
```
|
|
303
381
|
|
|
304
382
|
### Non-blocking logging
|
|
305
383
|
|
|
306
384
|
Since v1.0, log I/O is offloaded to a background thread by default. The native
|
|
307
|
-
Rust writer uses a bounded 8192-record queue
|
|
385
|
+
Rust writer uses a bounded 8192-record queue and waits for space while it is open. Set
|
|
308
386
|
`overflow="drop"` to favor caller latency, or explicitly pass `maxsize=0` only
|
|
309
387
|
when an unbounded queue is acceptable.
|
|
310
388
|
|
|
389
|
+
|
|
311
390
|
## Native runtime
|
|
312
391
|
|
|
313
392
|
structguru ships a required Rust extension that renders and enqueues logging
|
|
314
|
-
natively, off-thread. It is auto-enabled at import time
|
|
393
|
+
natively, off-thread. It is auto-enabled at import time, and importing the package
|
|
394
|
+
without the extension raises `RuntimeError`. The runtime does not depend on `orjson`;
|
|
315
395
|
exotic values (`datetime`, `UUID`, `Enum`, dataclasses) are converted natively
|
|
316
396
|
in Rust.
|
|
317
397
|
|
|
@@ -351,10 +431,10 @@ structguru.logger.bind(request_id="abc").info("order {id} accepted", id=987)
|
|
|
351
431
|
# → JSON line written to /var/log/app.log by a background writer thread
|
|
352
432
|
```
|
|
353
433
|
|
|
354
|
-
|
|
434
|
+
Import-time configuration and `configure()` without a Settings object honor environment variables:
|
|
355
435
|
|
|
356
436
|
```bash
|
|
357
|
-
|
|
437
|
+
STRUCTGURU_LEVEL=INFO STRUCTGURU_SERVICE=myapp python -m myapp
|
|
358
438
|
```
|
|
359
439
|
|
|
360
440
|
Invalid native environment values fail import with an actionable exception. This
|
|
@@ -364,20 +444,24 @@ Public API:
|
|
|
364
444
|
|
|
365
445
|
| Symbol | Purpose |
|
|
366
446
|
|--------|---------|
|
|
367
|
-
| `configure(...)` |
|
|
447
|
+
| `configure(...)` | Replace rendering, filtering, redaction, and output settings. |
|
|
448
|
+
| `Settings` | Validate reusable options; construct from Python values, a mapping, or the environment. |
|
|
449
|
+
| `get_config()` | Return configured options, or `None` when shut down. |
|
|
450
|
+
| `update(...)` | Change selected active options without rereading the environment. |
|
|
368
451
|
| `shutdown()` | Stop the writer; logging is disabled until `configure()` is called. |
|
|
369
452
|
| `set_level(level)` | Adjust the level threshold at runtime. |
|
|
370
|
-
| `writer_metrics()` |
|
|
371
|
-
| `
|
|
453
|
+
| `writer_metrics()` | Current writer counters (enqueued/written/dropped/depth/...) plus filter counters when active; `None` after shutdown. |
|
|
454
|
+
| `lifecycle_metrics()` | Cumulative native deliveries rejected by closed writers; available after shutdown and across reconfiguration. |
|
|
455
|
+
| `is_available()` | Compatibility helper; always `True` once the package has imported, because a missing extension fails import. |
|
|
372
456
|
|
|
373
457
|
Behavior notes:
|
|
374
458
|
|
|
375
|
-
- **Overflow**: the default `maxsize=8192` uses `overflow="block"` for
|
|
459
|
+
- **Overflow**: the default `maxsize=8192` uses `overflow="block"` to wait for queue space. Use `overflow="drop"` for drop-newest behavior with metrics and rate-limited warnings. `maxsize=0` explicitly opts into an unbounded queue.
|
|
376
460
|
- **Redaction, level filtering, exceptions, and OpenTelemetry** injection are supported natively; redaction covers the message and all structured string values before rendering or Sentry export. `sensitive_keys` overrides the default redaction keys. Rust's linear-time regex engine rejects backreferences and look-around with `ValueError` at configuration time.
|
|
377
461
|
- **Sampling & rate limiting** (`sample_rate`, `rate_limit_max`, `rate_limit_period`) are applied as native pre-render filters — dropped records cost zero rendering. `sampled` and `rate_limited` counters are distinct from the transport `dropped` counter. `sample_max_level` restricts sampling to records at or below that level; more severe records always pass.
|
|
378
462
|
- **Metric hooks** (`metric_processor=...`) invoke a structlog-style processor (e.g. `MetricProcessor`) for every *kept* record on the caller's thread, with `(None, method, {"event": message, **fields})`. Dropped records (level/sampling/rate-limit) never reach it; hook errors are swallowed.
|
|
379
463
|
- **Fork/shutdown safe** — the writer is flushed on exit and respawned in forked children (gunicorn/celery prefork). Rotating-file writers sharing a path coordinate through an owner-only `.lock` sidecar; distributed hosts should still prefer stdout and an external collector.
|
|
380
|
-
- **Structured exceptions** (`structured_exceptions=True`) render `type`, `message`, `module`, and frames as a dictionary, with optional redacted/truncated locals controlled by the `exception_*` options.
|
|
464
|
+
- **Structured exceptions** (`structured_exceptions=True`) render `type`, `message`, `module`, and frames as a dictionary, with optional redacted/truncated locals controlled by the `exception_*` options. Exception groups include nested members under `exceptions`, with the same frame limits and redaction. Traversal stops at ten nesting levels or 100 exception nodes; `exceptions_truncated` counts omitted direct children. Failed message conversions produce a marker instead of interrupting logging.
|
|
381
465
|
- **`stack_info` is supported natively**: the stack is captured in Python and rendered in the same position as `StackInfoRenderer` (`stack` between `service` and `message`). Unlike the standard path, the stack ends at the *user's* calling frame (structguru-internal frames are skipped, the way structlog skips its own).
|
|
382
466
|
- **Console mode** (`format="console"`): renders colored, human-readable lines instead of JSON — structguru's own stable dev format (`<timestamp> [<LEVEL>] <message> k=v`), with ANSI colors by default on a TTY. Override with `colors=True/False`.
|
|
383
467
|
- **File sinks** (`file_path=...`): write to a rotating file natively. Defaults mirror `RotatingFileHandler` (50 MB, 5 backups); configure via `file_max_bytes`/`file_backup_count`. Set `also_stdout=True` to mirror output to both file and stdout (e.g. container + persistent log).
|
|
@@ -385,6 +469,14 @@ Behavior notes:
|
|
|
385
469
|
- **Sentry integration** (`sentry_processor=SentryProcessor(...)`): receives the already-redacted event and raw `exc_info` only for exception capture.
|
|
386
470
|
- **Scope**: the native renderer covers JSON and console rendering, file/stdout/callable sinks, redaction, sampling/rate limiting, metrics, exceptions, and stack information. `logger.add()` sinks receive native and stdlib records.
|
|
387
471
|
|
|
472
|
+
Shutdown and reconfiguration drain records already accepted by the native queue.
|
|
473
|
+
Calls still formatting or waiting for space may be rejected when their writer closes,
|
|
474
|
+
even in block mode. `lifecycle_metrics()["rejected"]` counts these native deliveries
|
|
475
|
+
without queue-full warnings and survives shutdown and configuration changes; forked
|
|
476
|
+
children start at zero. Another destination, such as a synchronous stream, may already
|
|
477
|
+
have received the event. Calls begun while logging is disabled are no-ops and are not
|
|
478
|
+
counted. See the [lifecycle contract](docs/api/config.md#runtime-control) for details.
|
|
479
|
+
|
|
388
480
|
## Framework integrations
|
|
389
481
|
|
|
390
482
|
### ASGI (FastAPI / Starlette)
|
|
@@ -396,6 +488,13 @@ app = FastAPI()
|
|
|
396
488
|
app.add_middleware(StructguruMiddleware, request_id_header="X-Request-ID")
|
|
397
489
|
```
|
|
398
490
|
|
|
491
|
+
### Granian
|
|
492
|
+
|
|
493
|
+
Route Granian's server logs through `StructguruHandler` using `--log-config`,
|
|
494
|
+
and use `StructguruMiddleware` for ASGI request IDs and structured summaries.
|
|
495
|
+
The [Granian integration guide](docs/integrations.md#granian) includes a complete
|
|
496
|
+
logging dictionary, application and launcher examples, and access-log options.
|
|
497
|
+
|
|
399
498
|
### Celery
|
|
400
499
|
|
|
401
500
|
```python
|
|
@@ -448,11 +547,11 @@ server = grpc.server(
|
|
|
448
547
|
```python
|
|
449
548
|
import logging
|
|
450
549
|
|
|
451
|
-
from structguru import
|
|
550
|
+
from structguru import update
|
|
452
551
|
from structguru.integrations.sentry import SentryProcessor
|
|
453
552
|
|
|
454
553
|
sentry = SentryProcessor(event_level=logging.ERROR, tag_keys=frozenset({"service"}))
|
|
455
|
-
|
|
554
|
+
update(sentry_processor=sentry)
|
|
456
555
|
```
|
|
457
556
|
|
|
458
557
|
### Stdlib bridge
|