log-foundry 0.10.2.dev127__tar.gz → 0.10.2.dev129__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.
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/PKG-INFO +10 -6
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/README.md +9 -5
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/__init__.py +2 -2
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/config.py +8 -2
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/decorator.py +76 -4
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/ids.py +3 -1
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/kinesis.py +8 -3
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/LICENSE +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/_diag.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/_fork.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/_lifecycle.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/results.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/_retry.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/memory.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/null.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: log-foundry
|
|
3
|
-
Version: 0.10.2.
|
|
3
|
+
Version: 0.10.2.dev129
|
|
4
4
|
Summary: Generate logs for your console and JSON events for downstream consumption.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -347,7 +347,7 @@ lands can arrive at a sink whose close has already run, and nothing owes that si
|
|
|
347
347
|
|
|
348
348
|
### `@trace`
|
|
349
349
|
|
|
350
|
-
Decorate
|
|
350
|
+
Decorate a function, sync or async. Usable bare or with arguments:
|
|
351
351
|
|
|
352
352
|
```python
|
|
353
353
|
@lf.trace # span name = func.__qualname__
|
|
@@ -363,7 +363,10 @@ def process(): ...
|
|
|
363
363
|
|
|
364
364
|
`@trace` refuses at decoration what it cannot trace, with a `TypeError` naming the function:
|
|
365
365
|
a `@classmethod` or `@staticmethod` written *below* it (put them above), a bare string
|
|
366
|
-
(`@trace("checkout")` meant `@trace(name="checkout")`),
|
|
366
|
+
(`@trace("checkout")` meant `@trace(name="checkout")`), anything not callable, and a
|
|
367
|
+
**generator function** (sync or async, including one behind `@contextmanager`, `@lru_cache` or
|
|
368
|
+
any `functools.wraps` wrapper) — its body runs after the wrapper has returned, so the span would
|
|
369
|
+
close before it started. Trace the consumer instead: the function that iterates it.
|
|
367
370
|
|
|
368
371
|
The **outermost** decorated call starts a new trace; every nested decorated call becomes a
|
|
369
372
|
child span within it. On an exception, the decorator records `status="error"` plus an `error`
|
|
@@ -1083,7 +1086,7 @@ They tell you different things, and they want different responses:
|
|
|
1083
1086
|
|
|
1084
1087
|
| Field | Means | What to do |
|
|
1085
1088
|
|---|---|---|
|
|
1086
|
-
| `dropped` | The queue filled — the destination is not keeping up. Delivery continues. | Make the destination keep up: scale the sink, or reduce what you log. The worker's batch size, flush interval and queue depth are **not** reachable from the public API, so tuning them is not an option this version offers you. |
|
|
1089
|
+
| `dropped` | The queue filled — the destination is not keeping up. Delivery continues. It counts **submissions** — one per span close, or per open span a `flush()` sweeps — so the events lost are a multiple of it. | Make the destination keep up: scale the sink, or reduce what you log. The worker's batch size, flush interval and queue depth are **not** reachable from the public API, so tuning them is not an option this version offers you. |
|
|
1087
1090
|
| `failed_batches` | A sink stayed broken through the whole retry budget. Delivery continues. | Fix the destination. |
|
|
1088
1091
|
| `stopped_reason` | The background thread **died** on that exception type. Nothing further will be delivered, ever. | Restart the process; investigate the named exception. |
|
|
1089
1092
|
| `sink.dropped` | The sink discarded events **before** attempting delivery — an oversized record, or one the client refused outright. | Read the stderr line: it names the cause. An oversized record means shrink what you log; a refused local produce/publish (Kafka, Pub/Sub) points at the client — a saturated buffer, a bad topic, a credential. |
|
|
@@ -1092,12 +1095,13 @@ They tell you different things, and they want different responses:
|
|
|
1092
1095
|
| `incomplete_swaps` | A late `configure(sink=...)` could not confirm the previous sink was drained. The swap took effect; that sink was left open and some queued events may have gone to the new one. | Investigate the previous sink — it was hung or failing. Configure the sink before the first log where you can. |
|
|
1093
1096
|
| `inherited_sink` | This process is delivering to a sink it **inherited across a `fork`** and may not release, so it will not be closed here. Not a loss and not an alert term. | Nothing, usually. It explains a handle still open after `shutdown()`, and tells you a deployment shares one sink across a fork at all. `True` for a shared `StdoutSink` too, whose `close()` only flushes — so a `True` is not by itself evidence that anything is held. If you want the child to own its transport, build the sink in the worker process (see Forking). |
|
|
1094
1097
|
| `orphan_lost` | An event logged **with no open span** never reached the sink. That call emits on your own thread with no worker behind it, so no other field here can carry it — it is not a batch, there was no retry, and there may be no worker at all. Covers a sink that failed to *construct* as well as one that raised. | Fix the destination, or the data. The stderr line names the exception type. If a process logs this way at all, this is the field to alert on: nothing else describes that path. |
|
|
1095
|
-
| `in_span_lost` | An event logged **inside a span**
|
|
1098
|
+
| `in_span_lost` | An event logged **inside a span** was lost before delivery, for one of two reasons the count tells apart: **the data** — a value that could not be turned into an event, one per call — or **no drain thread at all** — the process could not start the worker, and the span's whole buffer is counted at once (SPEC-050). Never a destination that failed at delivery; that is `failed_batches`. | One event at a time: fix the call site — a non-string message (an exception object, say) is the common cause. A whole buffer at once: the process cannot start a thread; the stderr line names it. |
|
|
1096
1099
|
| `closing_sinks` | Swapped-out sinks inside `close()` **right now** — a live gauge, not a counter. It and `queued` are the two fields here that fall as well as rise; the other integer counters only climb. Non-zero on a single read is normal during a swap. | Nothing, unless it stays non-zero. That means a destination is stuck in `close()` and will not release its resources. |
|
|
1097
1100
|
|
|
1098
1101
|
`orphan_lost` and `in_span_lost` are deliberately two fields and their sum is deliberately not
|
|
1099
1102
|
reported. They aggregate different failure populations — one can mean the destination *or* the
|
|
1100
|
-
data
|
|
1103
|
+
data; the other never the destination — the data, or no drain thread at all (SPEC-050) — so a
|
|
1104
|
+
single number would hide which fix applies.
|
|
1101
1105
|
|
|
1102
1106
|
`h.sink` is a `SinkLosses`, carrying `dropped` and `failed`, or `None` — `None` when no worker
|
|
1103
1107
|
exists yet, or when the configured sink reports nothing (`losses()` is optional, and a sink whose
|
|
@@ -296,7 +296,7 @@ lands can arrive at a sink whose close has already run, and nothing owes that si
|
|
|
296
296
|
|
|
297
297
|
### `@trace`
|
|
298
298
|
|
|
299
|
-
Decorate
|
|
299
|
+
Decorate a function, sync or async. Usable bare or with arguments:
|
|
300
300
|
|
|
301
301
|
```python
|
|
302
302
|
@lf.trace # span name = func.__qualname__
|
|
@@ -312,7 +312,10 @@ def process(): ...
|
|
|
312
312
|
|
|
313
313
|
`@trace` refuses at decoration what it cannot trace, with a `TypeError` naming the function:
|
|
314
314
|
a `@classmethod` or `@staticmethod` written *below* it (put them above), a bare string
|
|
315
|
-
(`@trace("checkout")` meant `@trace(name="checkout")`),
|
|
315
|
+
(`@trace("checkout")` meant `@trace(name="checkout")`), anything not callable, and a
|
|
316
|
+
**generator function** (sync or async, including one behind `@contextmanager`, `@lru_cache` or
|
|
317
|
+
any `functools.wraps` wrapper) — its body runs after the wrapper has returned, so the span would
|
|
318
|
+
close before it started. Trace the consumer instead: the function that iterates it.
|
|
316
319
|
|
|
317
320
|
The **outermost** decorated call starts a new trace; every nested decorated call becomes a
|
|
318
321
|
child span within it. On an exception, the decorator records `status="error"` plus an `error`
|
|
@@ -1032,7 +1035,7 @@ They tell you different things, and they want different responses:
|
|
|
1032
1035
|
|
|
1033
1036
|
| Field | Means | What to do |
|
|
1034
1037
|
|---|---|---|
|
|
1035
|
-
| `dropped` | The queue filled — the destination is not keeping up. Delivery continues. | Make the destination keep up: scale the sink, or reduce what you log. The worker's batch size, flush interval and queue depth are **not** reachable from the public API, so tuning them is not an option this version offers you. |
|
|
1038
|
+
| `dropped` | The queue filled — the destination is not keeping up. Delivery continues. It counts **submissions** — one per span close, or per open span a `flush()` sweeps — so the events lost are a multiple of it. | Make the destination keep up: scale the sink, or reduce what you log. The worker's batch size, flush interval and queue depth are **not** reachable from the public API, so tuning them is not an option this version offers you. |
|
|
1036
1039
|
| `failed_batches` | A sink stayed broken through the whole retry budget. Delivery continues. | Fix the destination. |
|
|
1037
1040
|
| `stopped_reason` | The background thread **died** on that exception type. Nothing further will be delivered, ever. | Restart the process; investigate the named exception. |
|
|
1038
1041
|
| `sink.dropped` | The sink discarded events **before** attempting delivery — an oversized record, or one the client refused outright. | Read the stderr line: it names the cause. An oversized record means shrink what you log; a refused local produce/publish (Kafka, Pub/Sub) points at the client — a saturated buffer, a bad topic, a credential. |
|
|
@@ -1041,12 +1044,13 @@ They tell you different things, and they want different responses:
|
|
|
1041
1044
|
| `incomplete_swaps` | A late `configure(sink=...)` could not confirm the previous sink was drained. The swap took effect; that sink was left open and some queued events may have gone to the new one. | Investigate the previous sink — it was hung or failing. Configure the sink before the first log where you can. |
|
|
1042
1045
|
| `inherited_sink` | This process is delivering to a sink it **inherited across a `fork`** and may not release, so it will not be closed here. Not a loss and not an alert term. | Nothing, usually. It explains a handle still open after `shutdown()`, and tells you a deployment shares one sink across a fork at all. `True` for a shared `StdoutSink` too, whose `close()` only flushes — so a `True` is not by itself evidence that anything is held. If you want the child to own its transport, build the sink in the worker process (see Forking). |
|
|
1043
1046
|
| `orphan_lost` | An event logged **with no open span** never reached the sink. That call emits on your own thread with no worker behind it, so no other field here can carry it — it is not a batch, there was no retry, and there may be no worker at all. Covers a sink that failed to *construct* as well as one that raised. | Fix the destination, or the data. The stderr line names the exception type. If a process logs this way at all, this is the field to alert on: nothing else describes that path. |
|
|
1044
|
-
| `in_span_lost` | An event logged **inside a span**
|
|
1047
|
+
| `in_span_lost` | An event logged **inside a span** was lost before delivery, for one of two reasons the count tells apart: **the data** — a value that could not be turned into an event, one per call — or **no drain thread at all** — the process could not start the worker, and the span's whole buffer is counted at once (SPEC-050). Never a destination that failed at delivery; that is `failed_batches`. | One event at a time: fix the call site — a non-string message (an exception object, say) is the common cause. A whole buffer at once: the process cannot start a thread; the stderr line names it. |
|
|
1045
1048
|
| `closing_sinks` | Swapped-out sinks inside `close()` **right now** — a live gauge, not a counter. It and `queued` are the two fields here that fall as well as rise; the other integer counters only climb. Non-zero on a single read is normal during a swap. | Nothing, unless it stays non-zero. That means a destination is stuck in `close()` and will not release its resources. |
|
|
1046
1049
|
|
|
1047
1050
|
`orphan_lost` and `in_span_lost` are deliberately two fields and their sum is deliberately not
|
|
1048
1051
|
reported. They aggregate different failure populations — one can mean the destination *or* the
|
|
1049
|
-
data
|
|
1052
|
+
data; the other never the destination — the data, or no drain thread at all (SPEC-050) — so a
|
|
1053
|
+
single number would hide which fix applies.
|
|
1050
1054
|
|
|
1051
1055
|
`h.sink` is a `SinkLosses`, carrying `dropped` and `failed`, or `None` — `None` when no worker
|
|
1052
1056
|
exists yet, or when the configured sink reports nothing (`losses()` is optional, and a sink whose
|
|
@@ -74,7 +74,7 @@ keywords = [
|
|
|
74
74
|
# vulnerability-reporting channel. The repository is still named `log-forge` — the ORIGINAL name,
|
|
75
75
|
# which PyPI rejected for the distribution — so these URLs deliberately do not match the package
|
|
76
76
|
# name. See the note on `name` above before "correcting" them.
|
|
77
|
-
version = "0.10.2.
|
|
77
|
+
version = "0.10.2.dev129"
|
|
78
78
|
|
|
79
79
|
[project.urls]
|
|
80
80
|
Homepage = "https://github.com/agriffi10/log-forge"
|
|
@@ -87,8 +87,8 @@ def health() -> Health:
|
|
|
87
87
|
an event that cannot be *built* never reaches a queue at all — so every other field here
|
|
88
88
|
describes machinery those two losses never touched, and a process that only logs outside a
|
|
89
89
|
span read all zeros over total loss until they existed. They stay separate because one can
|
|
90
|
-
mean the destination or the data and the other
|
|
91
|
-
nobody can act on.
|
|
90
|
+
mean the destination or the data and the other never the destination — the data, or no drain
|
|
91
|
+
thread at all (SPEC-050 FR-003); their sum is a number nobody can act on.
|
|
92
92
|
|
|
93
93
|
``retired`` alone is not a fault — a process that shuts down and then stops logging is
|
|
94
94
|
doing the right thing, which is why it is paired with the count rather than alerted on.
|
|
@@ -271,7 +271,12 @@ def get_config() -> Config:
|
|
|
271
271
|
validation :func:`configure` performs — ``max_value_bytes = 0`` was accepted and emptied
|
|
272
272
|
every event it touched. Both measured. :func:`configure` is the only route to a change.
|
|
273
273
|
|
|
274
|
-
It is a **copy**, not the frozen original, and ``defaults`` is copied with it
|
|
274
|
+
It is a **copy**, not the frozen original, and ``defaults`` is copied with it — **one level
|
|
275
|
+
deep**: the mapping is the caller's own, its values are shared with the library, so a nested
|
|
276
|
+
mutable value edited through the copy still reaches every later event (2026-09-04 audit,
|
|
277
|
+
N8). Deep-copying was declined because a default can be any object and copying an arbitrary
|
|
278
|
+
object can raise or be wrong; a caller who needs isolation for a nested value copies it. A
|
|
279
|
+
caller who
|
|
275
280
|
defeats the freeze — ``object.__setattr__`` reaches through any frozen dataclass — then
|
|
276
281
|
edits an object the library does not read, rather than the live config; and ``defaults`` is
|
|
277
282
|
a plain mutable ``dict``, so sharing it would leave the freeze cosmetic at the one field
|
|
@@ -282,7 +287,8 @@ def get_config() -> Config:
|
|
|
282
287
|
None.
|
|
283
288
|
|
|
284
289
|
Returns:
|
|
285
|
-
A copy of the process-wide :class:`Config`, with its own ``defaults
|
|
290
|
+
A copy of the process-wide :class:`Config`, with its own top-level ``defaults`` mapping
|
|
291
|
+
whose values are shared.
|
|
286
292
|
|
|
287
293
|
Raises:
|
|
288
294
|
None.
|
|
@@ -639,6 +639,21 @@ def _refuse_unusable(fn: object) -> None:
|
|
|
639
639
|
``self`` to a function declared without one. A ``str`` is the slip ``@trace("checkout")``,
|
|
640
640
|
which meant ``name=``. Anything else that is not callable is refused by type.
|
|
641
641
|
|
|
642
|
+
A generator function or an async generator function is refused too (SPEC-055 FR-003): its
|
|
643
|
+
body runs after the wrapper has returned the generator object, so the span would open and
|
|
644
|
+
close before a single line of it ran, and every event the body logged would be an orphan on
|
|
645
|
+
a fresh trace — measured, ``['span.start', 'span.end']`` before the first ``next()``. The
|
|
646
|
+
check is the code flags, on ``fn``, on whatever ``fn`` advertises through ``__wrapped__``
|
|
647
|
+
— so ``@trace`` above ``@contextmanager``, ``@lru_cache`` or any ``functools.wraps`` wrapper
|
|
648
|
+
of a generator is refused too, each measured recording the same orphaned ``inside`` event —
|
|
649
|
+
and on the type's ``__call__`` for a callable instance; all of them see through a
|
|
650
|
+
``functools.partial``. A plain function that merely *returns* a generator object without
|
|
651
|
+
advertising it is indistinguishable here and is not detected, which is a stated limit
|
|
652
|
+
rather than a gap to close at call time, where invariant 13 does not refuse. Wrapping the
|
|
653
|
+
iteration instead — a span per generator, pushed around every resumption — was considered
|
|
654
|
+
and deferred: a refusal can be lifted into a wrap later without breaking anyone, while a
|
|
655
|
+
wrap shipped first freezes its semantics at 1.0.
|
|
656
|
+
|
|
642
657
|
Args:
|
|
643
658
|
fn: Whatever was handed to the decorator.
|
|
644
659
|
|
|
@@ -646,8 +661,8 @@ def _refuse_unusable(fn: object) -> None:
|
|
|
646
661
|
None.
|
|
647
662
|
|
|
648
663
|
Raises:
|
|
649
|
-
TypeError: If ``fn`` is a ``classmethod`` or ``staticmethod`` object, a ``str``,
|
|
650
|
-
callable at all.
|
|
664
|
+
TypeError: If ``fn`` is a ``classmethod`` or ``staticmethod`` object, a ``str``, not
|
|
665
|
+
callable at all, or a generator function or async generator function.
|
|
651
666
|
Exception: Whatever the callable's own attribute access raises — a ``__class__`` property,
|
|
652
667
|
say — at decoration, in the caller's frame, which is invariant 13's moment.
|
|
653
668
|
"""
|
|
@@ -664,6 +679,61 @@ def _refuse_unusable(fn: object) -> None:
|
|
|
664
679
|
)
|
|
665
680
|
if not callable(fn):
|
|
666
681
|
raise TypeError(f"@trace needs a callable, got {type(fn).__name__}")
|
|
682
|
+
if _is_generator_function(fn):
|
|
683
|
+
raise TypeError(
|
|
684
|
+
f"@trace cannot trace the iteration of the generator function {_span_name(fn)}: its "
|
|
685
|
+
f"body runs after the wrapper has returned, so the span would close before it "
|
|
686
|
+
f"starts. Trace the consumer instead: the function that iterates it"
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def _is_generator_function(fn: object) -> bool:
|
|
691
|
+
"""Reports whether a callable's body is a generator or async generator (SPEC-055 FR-003).
|
|
692
|
+
|
|
693
|
+
The same shape as :func:`_is_async`: the code flags of ``fn`` itself, which see through a
|
|
694
|
+
``functools.partial``; then of whatever ``fn`` advertises through ``__wrapped__``, which is
|
|
695
|
+
how ``@contextmanager``, ``@lru_cache`` and every ``functools.wraps`` wrapper point back at
|
|
696
|
+
the generator they wrap; and for anything that is not a plain function the type's
|
|
697
|
+
``__call__`` as well, so a callable instance whose ``__call__`` yields is caught too. The
|
|
698
|
+
unwrap is guarded because :func:`inspect.unwrap` raises ``ValueError`` on a ``__wrapped__``
|
|
699
|
+
cycle, and a cycle is not a generator.
|
|
700
|
+
|
|
701
|
+
Args:
|
|
702
|
+
fn: The callable being decorated.
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
Whether ``fn`` would return a generator or async generator object when called.
|
|
706
|
+
|
|
707
|
+
Raises:
|
|
708
|
+
Exception: Whatever the callable's own ``__class__``, ``__call__`` or ``__wrapped__``
|
|
709
|
+
lookup raises, at decoration, in the caller's frame.
|
|
710
|
+
"""
|
|
711
|
+
if _yields(fn):
|
|
712
|
+
return True
|
|
713
|
+
try:
|
|
714
|
+
target = inspect.unwrap(cast("Callable[..., Any]", fn))
|
|
715
|
+
except ValueError:
|
|
716
|
+
target = fn
|
|
717
|
+
if target is not fn and _yields(target):
|
|
718
|
+
return True
|
|
719
|
+
if inspect.isfunction(fn):
|
|
720
|
+
return False
|
|
721
|
+
return _yields(type(fn).__call__)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def _yields(fn: object) -> bool:
|
|
725
|
+
"""Reads a callable's code flags for a generator or async generator body.
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
fn: The callable to inspect.
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
Whether its body is a generator or an async generator.
|
|
732
|
+
|
|
733
|
+
Raises:
|
|
734
|
+
None.
|
|
735
|
+
"""
|
|
736
|
+
return inspect.isgeneratorfunction(fn) or inspect.isasyncgenfunction(fn)
|
|
667
737
|
|
|
668
738
|
|
|
669
739
|
def _span_name(fn: object) -> str:
|
|
@@ -749,8 +819,10 @@ def trace(
|
|
|
749
819
|
|
|
750
820
|
Raises:
|
|
751
821
|
TypeError: At decoration, for a ``classmethod`` or ``staticmethod`` object (the decorators
|
|
752
|
-
are in the wrong order), a ``str`` (``name=`` was meant), anything not callable,
|
|
753
|
-
``name=`` that is not a ``str`` (SPEC-055 FR-002,
|
|
822
|
+
are in the wrong order), a ``str`` (``name=`` was meant), anything not callable, a
|
|
823
|
+
``name=`` that is not a ``str`` (SPEC-055 FR-002), or a generator function, whose
|
|
824
|
+
iteration cannot be traced by a wrapper around the call (SPEC-055 FR-003) — invariant
|
|
825
|
+
13.
|
|
754
826
|
"""
|
|
755
827
|
span_defaults = None if defaults is None else dict(defaults)
|
|
756
828
|
|
|
@@ -137,7 +137,9 @@ def parse_traceparent(value: object) -> tuple[str, str] | None:
|
|
|
137
137
|
its own identity.
|
|
138
138
|
|
|
139
139
|
Args:
|
|
140
|
-
value: An inbound header value of any type, from outside the process.
|
|
140
|
+
value: An inbound header value of any type, from outside the process. Surrounding
|
|
141
|
+
whitespace is stripped before parsing, since a header value arrives trimmed from some
|
|
142
|
+
transports and not others; whitespace anywhere else is a malformed field.
|
|
141
143
|
|
|
142
144
|
Returns:
|
|
143
145
|
A ``(trace_id, span_id)`` tuple, or ``None`` if the value is unusable.
|
|
@@ -282,11 +282,15 @@ class KinesisSink:
|
|
|
282
282
|
|
|
283
283
|
|
|
284
284
|
MAX_PARTITION_KEY_BYTES = 256
|
|
285
|
-
"""
|
|
285
|
+
"""This sink's ceiling on a ``PutRecords`` partition key, in UTF-8 bytes.
|
|
286
|
+
|
|
287
|
+
At or below the service's own limit, which its API reference states as 256 **characters**: a
|
|
288
|
+
byte bound is stricter for any non-ASCII key and therefore safe (2026-09-04 audit, N12).
|
|
289
|
+
"""
|
|
286
290
|
|
|
287
291
|
|
|
288
292
|
def _partition_key(raw: str) -> str:
|
|
289
|
-
"""Bounds a partition key to
|
|
293
|
+
"""Bounds a partition key to 256 UTF-8 **bytes**, at or below the service's 256-character limit.
|
|
290
294
|
|
|
291
295
|
Both encodes carry an ``errors=`` and both are load-bearing. Assembly replaces a lone
|
|
292
296
|
surrogate since SPEC-055 FR-001, so an event field cannot carry one — but this key is derived
|
|
@@ -319,7 +323,8 @@ def _record_size(record: dict[str, Any]) -> int:
|
|
|
319
323
|
"""Measures one request entry, partition key included (SPEC-038 FR-009).
|
|
320
324
|
|
|
321
325
|
``PutRecords`` charges the partition key against the 5 MiB request limit, and a key may be up
|
|
322
|
-
to 256 bytes, so a 500-record request could
|
|
326
|
+
to 256 characters at the service (bounded here to 256 bytes), so a 500-record request could
|
|
327
|
+
understate itself by ~128 KB — enough to have the
|
|
323
328
|
service reject a chunk this sink believed was inside the budget. ``SQSSink`` charges its FIFO
|
|
324
329
|
ids for the same reason and records the same rationale.
|
|
325
330
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/elasticsearch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{log_foundry-0.10.2.dev127 → log_foundry-0.10.2.dev129}/src/log_foundry/sinks/logging_sink.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|