log-foundry 0.10.2.dev7__tar.gz → 0.10.2.dev9__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.dev7 → log_foundry-0.10.2.dev9}/PKG-INFO +1 -1
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/pyproject.toml +1 -1
- log_foundry-0.10.2.dev9/src/log_foundry/_diag.py +53 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/api.py +31 -12
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/context.py +14 -2
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/decorator.py +122 -26
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/LICENSE +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/README.md +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/sinks/util.py +0 -0
- {log_foundry-0.10.2.dev7 → log_foundry-0.10.2.dev9}/src/log_foundry/worker.py +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""The library's own diagnostic channel — one line on stderr, for failures it absorbed.
|
|
2
|
+
|
|
3
|
+
This library *is* the logger, so it cannot report its own faults through itself: an event
|
|
4
|
+
describing a broken sink would be handed to that same broken sink. Stderr is the channel of last
|
|
5
|
+
resort, and every site that swallows an exception to keep architecture §4's promise (logging never
|
|
6
|
+
breaks the application) announces it through here, so the wording and the safety rules are decided
|
|
7
|
+
once rather than per call site.
|
|
8
|
+
|
|
9
|
+
Two rules, both load-bearing:
|
|
10
|
+
|
|
11
|
+
* **The exception's type, never its message.** A message can carry a value from the event that
|
|
12
|
+
provoked it, and arch §6 keeps user data out of anything the library emits about itself — the
|
|
13
|
+
same rule SPEC-019 applies to ``Health.stopped_reason``. A type name is enough to tell a
|
|
14
|
+
``ConnectionError`` from a ``TypeError``, which is what the reader needs.
|
|
15
|
+
* **No stream fault escapes.** It is called from ``except`` and ``finally`` blocks whose entire
|
|
16
|
+
purpose is to stop an exception reaching the caller, so a failure here — a closed stderr at
|
|
17
|
+
interpreter shutdown, a stream that rejects the write, ``sys.stderr`` set to ``None`` — must not
|
|
18
|
+
become the exception those guards existed to prevent. A ``BaseException`` still passes through,
|
|
19
|
+
as everywhere else in this library: a ``KeyboardInterrupt`` landing mid-write is the operator's
|
|
20
|
+
intent, not a stream fault to swallow.
|
|
21
|
+
|
|
22
|
+
SPEC-029 takes ownership of this module and moves the ``repr(exception)`` sink sites onto it.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import sys
|
|
28
|
+
|
|
29
|
+
__all__ = ["absorbed"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def absorbed(where: str, exc: BaseException, detail: str = "") -> None:
|
|
33
|
+
"""Report a failure the library swallowed rather than propagated.
|
|
34
|
+
|
|
35
|
+
Never raises on a stream fault; a ``BaseException`` from the write still propagates.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
where: What was being attempted, as a participle phrase that reads after "while" —
|
|
39
|
+
``"closing a span"``, ``"emitting an orphan log"``.
|
|
40
|
+
exc: The absorbed exception. Only ``type(exc).__name__`` is written.
|
|
41
|
+
detail: Optional consequence for the reader, appended after a semicolon — most usefully
|
|
42
|
+
what was lost, since an absorbed failure is invisible apart from this line.
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
suffix = f"; {detail}" if detail else ""
|
|
46
|
+
sys.stderr.write(
|
|
47
|
+
f"log-foundry: absorbed a failure while {where} ({type(exc).__name__}){suffix}\n"
|
|
48
|
+
)
|
|
49
|
+
except Exception:
|
|
50
|
+
# The channel of last resort has no fallback of its own. Reporting is best-effort by
|
|
51
|
+
# construction: losing the line is bad, raising from the guard that exists to protect
|
|
52
|
+
# the caller's exception would be worse (arch §4).
|
|
53
|
+
pass
|
|
@@ -12,7 +12,7 @@ the sink via the normal path.
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
-
from log_foundry import context
|
|
15
|
+
from log_foundry import _diag, context
|
|
16
16
|
from log_foundry.config import _ensure_sink
|
|
17
17
|
from log_foundry.console import ConsoleWriter
|
|
18
18
|
from log_foundry.context import set_baggage
|
|
@@ -40,9 +40,16 @@ def _log(level: str, message: str, echo: bool, fields: dict[str, object]) -> Non
|
|
|
40
40
|
With no active span (orphan) it becomes a standalone one-event span — fresh ``trace_id``,
|
|
41
41
|
``parent_span_id=None`` — flushed directly so nothing is dropped (FR-004). ``echo`` writes
|
|
42
42
|
the same event as a human-readable console line, additively.
|
|
43
|
+
|
|
44
|
+
The orphan branch is the one that reaches the sink **on the caller's own thread**, with no
|
|
45
|
+
worker between them to absorb a failure, so it is guarded end to end (SPEC-025 FR-003): a
|
|
46
|
+
bare ``log_foundry.info(...)`` must not hand the application a ``ConnectionError`` from a
|
|
47
|
+
destination the application never chose to talk to. The in-span branch is deliberately left
|
|
48
|
+
untouched — it only appends to a list, and FR-003 requires no behaviour change there.
|
|
43
49
|
"""
|
|
44
50
|
baggage = context.get_baggage()
|
|
45
51
|
span = context.current_span()
|
|
52
|
+
event: dict[str, object] | None = None
|
|
46
53
|
if span is not None:
|
|
47
54
|
event = build_event(span, level, message, fields=fields, baggage=baggage)
|
|
48
55
|
span.events.append(event)
|
|
@@ -51,17 +58,29 @@ def _log(level: str, message: str, echo: bool, fields: dict[str, object]) -> Non
|
|
|
51
58
|
# log is recorded, not silently dropped. Resolve the sink via _ensure_sink (as the
|
|
52
59
|
# decorator does) so a zero-config orphan log falls back to StdoutSink instead of
|
|
53
60
|
# crashing. SPEC-004's worker will later own this direct handoff.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
try:
|
|
62
|
+
orphan = Span(
|
|
63
|
+
trace_id=new_trace_id(),
|
|
64
|
+
span_id=new_span_id(),
|
|
65
|
+
parent_span_id=None,
|
|
66
|
+
name=message,
|
|
67
|
+
start_ts=0.0,
|
|
68
|
+
)
|
|
69
|
+
event = build_event(orphan, level, message, fields=fields, baggage=baggage)
|
|
70
|
+
_ensure_sink().emit([event])
|
|
71
|
+
except Exception as exc:
|
|
72
|
+
# The whole branch, not just `emit`: `_ensure_sink` constructs the sink on first
|
|
73
|
+
# use, so a sink that fails to *build* raises here too — and that is the shape the
|
|
74
|
+
# spec's own motivating case takes.
|
|
75
|
+
_diag.absorbed("emitting an orphan log", exc, "the event was lost")
|
|
76
|
+
if echo and event is not None:
|
|
77
|
+
try:
|
|
78
|
+
_console.write(event)
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
# An echo is a diagnostic convenience and must not outrank the caller: the stream
|
|
81
|
+
# may be closed, redirected, or gone at interpreter shutdown. It runs *after* the
|
|
82
|
+
# emit, so a broken console never costs the event itself.
|
|
83
|
+
_diag.absorbed("echoing to the console", exc)
|
|
65
84
|
|
|
66
85
|
|
|
67
86
|
def debug(message: str, *, echo: bool = False, **fields: object) -> None:
|
|
@@ -91,8 +91,20 @@ def push_span(span: Span) -> contextvars.Token[tuple[Span, ...]]:
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def pop_span(token: contextvars.Token[tuple[Span, ...]]) -> None:
|
|
94
|
-
"""Restore the span stack to its state before the matching :func:`push_span`.
|
|
95
|
-
|
|
94
|
+
"""Restore the span stack to its state before the matching :func:`push_span`.
|
|
95
|
+
|
|
96
|
+
Total — never raises, on the same terms and for the same reason as
|
|
97
|
+
:func:`pop_baggage_scope`: it runs in the decorator's ``finally``, where an exception would
|
|
98
|
+
replace the one the caller's own function raised (arch §4, SPEC-025).
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
_span_stack.reset(token)
|
|
102
|
+
except Exception:
|
|
103
|
+
# `reset` rejects a token minted in another context and one already used. Restoring the
|
|
104
|
+
# captured stack directly is equivalent; anything that is not a tuple of spans cannot
|
|
105
|
+
# have come from `push_span`, so it falls back to empty rather than corrupting the stack.
|
|
106
|
+
old = getattr(token, "old_value", None)
|
|
107
|
+
_span_stack.set(old if isinstance(old, tuple) else ())
|
|
96
108
|
|
|
97
109
|
|
|
98
110
|
def get_baggage() -> dict[str, object]:
|
|
@@ -28,9 +28,9 @@ import sys
|
|
|
28
28
|
import threading
|
|
29
29
|
from collections.abc import Callable
|
|
30
30
|
from time import monotonic
|
|
31
|
-
from typing import Any, TypeVar, cast, overload
|
|
31
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast, overload
|
|
32
32
|
|
|
33
|
-
from log_foundry import context
|
|
33
|
+
from log_foundry import _diag, context
|
|
34
34
|
from log_foundry.config import _ensure_sink
|
|
35
35
|
from log_foundry.ids import (
|
|
36
36
|
is_valid_span_id,
|
|
@@ -42,6 +42,9 @@ from log_foundry.ids import (
|
|
|
42
42
|
from log_foundry.model import Span, backfill_baggage, end_event, start_event
|
|
43
43
|
from log_foundry.worker import Health, Worker
|
|
44
44
|
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
import contextvars
|
|
47
|
+
|
|
45
48
|
__all__ = ["continue_trace", "trace"]
|
|
46
49
|
|
|
47
50
|
# Bound on how much of a rejected inbound value is echoed into a stderr warning.
|
|
@@ -272,6 +275,97 @@ def _flush(span: Span) -> None:
|
|
|
272
275
|
_get_worker().submit(span.events)
|
|
273
276
|
|
|
274
277
|
|
|
278
|
+
type _SpanScope = tuple[
|
|
279
|
+
Span | None,
|
|
280
|
+
contextvars.Token[tuple[Span, ...]] | None,
|
|
281
|
+
contextvars.Token[dict[str, object]] | None,
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _begin(name: str, defaults: dict[str, object] | None) -> _SpanScope:
|
|
286
|
+
"""Open a span and make it current, degrading rather than failing. Never raises.
|
|
287
|
+
|
|
288
|
+
Everything here runs **before** the caller's function body, so a failure would mean the
|
|
289
|
+
library prevented the application from doing its work at all — the worst reading of arch §4,
|
|
290
|
+
and worse than the close-path faults (SPEC-025 FR-001) because nothing has run yet.
|
|
291
|
+
|
|
292
|
+
Whatever succeeded is kept, and :func:`_end` releases exactly that much. Four shapes reach
|
|
293
|
+
it, and the *order of the three steps* is what keeps every one of them coherent:
|
|
294
|
+
|
|
295
|
+
* ``(span, token, scope)`` — everything worked; ``scope`` is ``None`` for a nested call,
|
|
296
|
+
which has no root scope to open.
|
|
297
|
+
* ``(span, token, None)`` — a nested call, or a root whose scope failed. **The scope is
|
|
298
|
+
taken first for this reason**: opened last, a root call that lost only its scope would
|
|
299
|
+
keep the span *and* leak its baggage into the next request, which is the SPEC-024 defect
|
|
300
|
+
reappearing through a failure path. Taken first, a lost scope means no span either.
|
|
301
|
+
* ``(None, None, scope)`` — the span could not be opened; the call runs untraced and the
|
|
302
|
+
scope is still released, so nothing leaks.
|
|
303
|
+
* ``(None, None, None)`` — nothing was set up.
|
|
304
|
+
|
|
305
|
+
A span that exists but was never pushed (``token is None``) is still closed and flushed by
|
|
306
|
+
:func:`_end`: it is not *current*, so nested calls will not parent to it and its own events
|
|
307
|
+
are all it carries, but that is more than discarding it would preserve.
|
|
308
|
+
|
|
309
|
+
The root test reads :func:`~log_foundry.context.current_span` before :func:`_open_span`,
|
|
310
|
+
which is what makes the new span current (SPEC-024).
|
|
311
|
+
"""
|
|
312
|
+
span: Span | None = None
|
|
313
|
+
token: contextvars.Token[tuple[Span, ...]] | None = None
|
|
314
|
+
scope: contextvars.Token[dict[str, object]] | None = None
|
|
315
|
+
try:
|
|
316
|
+
if context.current_span() is None:
|
|
317
|
+
scope = context.push_baggage_scope()
|
|
318
|
+
span = _open_span(name, defaults)
|
|
319
|
+
token = context.push_span(span)
|
|
320
|
+
except Exception as exc:
|
|
321
|
+
_diag.absorbed(
|
|
322
|
+
"opening a span",
|
|
323
|
+
exc,
|
|
324
|
+
"this call runs untraced" if span is None else "this call is traced incompletely",
|
|
325
|
+
)
|
|
326
|
+
return span, token, scope
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _end(
|
|
330
|
+
span: Span | None,
|
|
331
|
+
token: contextvars.Token[tuple[Span, ...]] | None,
|
|
332
|
+
scope: contextvars.Token[dict[str, object]] | None,
|
|
333
|
+
status: str,
|
|
334
|
+
error: BaseException | None,
|
|
335
|
+
) -> None:
|
|
336
|
+
"""Close the span and release the context. Never raises on a library fault.
|
|
337
|
+
|
|
338
|
+
Called from the wrappers' ``finally`` **once** per span, with the outcome the body actually
|
|
339
|
+
had (SPEC-025 FR-002) — the previous shape closed once in the ``try`` and again in the
|
|
340
|
+
``except``, so a close that failed on the success path emitted a second, contradicting
|
|
341
|
+
``span.end`` for a call that had returned normally.
|
|
342
|
+
|
|
343
|
+
The catch is ``Exception``, never ``BaseException``: a ``KeyboardInterrupt`` or
|
|
344
|
+
``SystemExit`` arriving here is the operator's or the runtime's intent and must still reach
|
|
345
|
+
the caller. That is the same line SPEC-019 drew in the opposite direction for the worker
|
|
346
|
+
thread, where the *absence* of a handler was the defect.
|
|
347
|
+
|
|
348
|
+
One ordering constraint, and only one: the baggage scope is released **after** the close, so
|
|
349
|
+
SPEC-015's backfill still reads the baggage that was live inside the span. The stack pop is
|
|
350
|
+
order-independent — ``_close_span`` reads the span it is handed and the current baggage,
|
|
351
|
+
never the stack — and simply mirrors the setup. Both releases are total in their own right
|
|
352
|
+
(SPEC-024, SPEC-025), so neither needs a guard here.
|
|
353
|
+
|
|
354
|
+
The ``is not None`` checks are load-bearing, not defensive tidiness: ``pop_span(None)`` would
|
|
355
|
+
fall through that function's own guard to ``set(())`` and wipe the whole stack, detaching the
|
|
356
|
+
parent of an untraced *nested* call and splitting its trace.
|
|
357
|
+
"""
|
|
358
|
+
if span is not None:
|
|
359
|
+
try:
|
|
360
|
+
_close_span(span, status, error)
|
|
361
|
+
except Exception as exc:
|
|
362
|
+
_diag.absorbed("closing a span", exc, "the span's events were lost")
|
|
363
|
+
if token is not None:
|
|
364
|
+
context.pop_span(token)
|
|
365
|
+
if scope is not None:
|
|
366
|
+
context.pop_baggage_scope(scope)
|
|
367
|
+
|
|
368
|
+
|
|
275
369
|
def _close_span(span: Span, status: str, exc: BaseException | None) -> None:
|
|
276
370
|
"""Append the end event, complete the boundary events' baggage, then flush.
|
|
277
371
|
|
|
@@ -311,47 +405,49 @@ def trace(
|
|
|
311
405
|
|
|
312
406
|
@functools.wraps(fn)
|
|
313
407
|
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
span = _open_span(name or fn.__qualname__, defaults)
|
|
317
|
-
token = context.push_span(span)
|
|
318
|
-
scope = context.push_baggage_scope() if is_root else None
|
|
408
|
+
span, token, scope = _begin(name or fn.__qualname__, defaults)
|
|
409
|
+
status, error = "ok", None
|
|
319
410
|
try:
|
|
320
411
|
result = await fn(*args, **kwargs)
|
|
321
|
-
_close_span(span, "ok", None)
|
|
322
|
-
return result
|
|
323
412
|
except BaseException as exc:
|
|
324
|
-
# Non-swallowing (arch §4): record the
|
|
413
|
+
# Non-swallowing (arch §4): record the outcome, then re-raise unchanged.
|
|
325
414
|
# BaseException covers asyncio.CancelledError — a cancelled coroutine is
|
|
326
415
|
# recorded as an error end event, never left with an unclosed span.
|
|
327
|
-
|
|
416
|
+
status, error = "error", exc
|
|
328
417
|
raise
|
|
329
418
|
finally:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
419
|
+
try:
|
|
420
|
+
_end(span, token, scope, status, error)
|
|
421
|
+
finally:
|
|
422
|
+
# `except ... as exc` auto-deletes its target for exactly this reason.
|
|
423
|
+
# `error` holds an exception whose traceback holds this frame, so
|
|
424
|
+
# keeping it past the handler builds a cycle only the collector can
|
|
425
|
+
# break — and pins the caller's own frames and locals along with it.
|
|
426
|
+
del status, error
|
|
427
|
+
return result
|
|
333
428
|
|
|
334
429
|
return cast("F", async_wrapper)
|
|
335
430
|
|
|
336
431
|
@functools.wraps(fn)
|
|
337
432
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
span = _open_span(name or fn.__qualname__, defaults)
|
|
341
|
-
token = context.push_span(span)
|
|
342
|
-
scope = context.push_baggage_scope() if is_root else None
|
|
433
|
+
span, token, scope = _begin(name or fn.__qualname__, defaults)
|
|
434
|
+
status, error = "ok", None
|
|
343
435
|
try:
|
|
344
436
|
result = fn(*args, **kwargs)
|
|
345
|
-
_close_span(span, "ok", None)
|
|
346
|
-
return result
|
|
347
437
|
except BaseException as exc:
|
|
348
|
-
# Non-swallowing (arch §4): record the
|
|
349
|
-
|
|
438
|
+
# Non-swallowing (arch §4): record the outcome, then re-raise unchanged.
|
|
439
|
+
status, error = "error", exc
|
|
350
440
|
raise
|
|
351
441
|
finally:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
442
|
+
try:
|
|
443
|
+
_end(span, token, scope, status, error)
|
|
444
|
+
finally:
|
|
445
|
+
# `except ... as exc` auto-deletes its target for exactly this reason.
|
|
446
|
+
# `error` holds an exception whose traceback holds this frame, so keeping
|
|
447
|
+
# it past the handler builds a cycle only the collector can break — and
|
|
448
|
+
# pins the caller's own frames and locals along with it.
|
|
449
|
+
del status, error
|
|
450
|
+
return result
|
|
355
451
|
|
|
356
452
|
return cast("F", wrapper)
|
|
357
453
|
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|