log-foundry 0.10.2.dev2__tar.gz → 0.10.2.dev4__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.
Files changed (53) hide show
  1. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/PKG-INFO +1 -1
  2. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/pyproject.toml +1 -1
  3. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/context.py +52 -0
  4. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/decorator.py +16 -0
  5. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/LICENSE +0 -0
  6. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/README.md +0 -0
  7. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/__init__.py +0 -0
  8. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/api.py +0 -0
  9. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/config.py +0 -0
  10. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/console.py +0 -0
  11. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/ids.py +0 -0
  12. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/model.py +0 -0
  13. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/py.typed +0 -0
  14. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sanitize.py +0 -0
  15. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/__init__.py +0 -0
  16. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/_batch.py +0 -0
  17. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/_chunk.py +0 -0
  18. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/_socket.py +0 -0
  19. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/_time.py +0 -0
  20. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/base.py +0 -0
  21. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/callback.py +0 -0
  22. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/clickhouse.py +0 -0
  23. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/datadog.py +0 -0
  24. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/elasticsearch.py +0 -0
  25. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/eventhubs.py +0 -0
  26. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/file.py +0 -0
  27. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/filtering.py +0 -0
  28. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/firehose.py +0 -0
  29. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/honeycomb.py +0 -0
  30. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/http.py +0 -0
  31. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/kafka.py +0 -0
  32. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/kinesis.py +0 -0
  33. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/logging_sink.py +0 -0
  34. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/logstash.py +0 -0
  35. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/loki.py +0 -0
  36. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/mongodb.py +0 -0
  37. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/multi.py +0 -0
  38. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/nats.py +0 -0
  39. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/newrelic.py +0 -0
  40. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/postgres.py +0 -0
  41. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/pubsub.py +0 -0
  42. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/rabbitmq.py +0 -0
  43. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/redis.py +0 -0
  44. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/sentry.py +0 -0
  45. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/sns.py +0 -0
  46. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/splunk.py +0 -0
  47. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/sqlite.py +0 -0
  48. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/sqs.py +0 -0
  49. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/stdout.py +0 -0
  50. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/syslog.py +0 -0
  51. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/transform.py +0 -0
  52. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/src/log_foundry/sinks/util.py +0 -0
  53. {log_foundry-0.10.2.dev2 → log_foundry-0.10.2.dev4}/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.dev2
3
+ Version: 0.10.2.dev4
4
4
  Summary: Generate logs for your console and JSON events for downstream consumption.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -20,7 +20,7 @@ dependencies = [
20
20
  ]
21
21
 
22
22
  # Optional features. Install with: pip install log-foundry[aws]
23
- version = "0.10.2.dev2"
23
+ version = "0.10.2.dev4"
24
24
 
25
25
  [project.optional-dependencies]
26
26
  aws = ["boto3>=1.43.61"] # SQSSink, SNSSink, KinesisSink, FirehoseSink
@@ -29,7 +29,9 @@ __all__ = [
29
29
  "current_trace_context",
30
30
  "current_traceparent",
31
31
  "get_baggage",
32
+ "pop_baggage_scope",
32
33
  "pop_span",
34
+ "push_baggage_scope",
33
35
  "push_span",
34
36
  "set_baggage",
35
37
  ]
@@ -81,6 +83,56 @@ def set_baggage(**kv: object) -> None:
81
83
  _baggage.set({**_baggage.get(), **kv})
82
84
 
83
85
 
86
+ # -- the root-span scope (SPEC-024) ------------------------------------------------------
87
+
88
+
89
+ def push_baggage_scope() -> contextvars.Token[dict[str, object]]:
90
+ """Open a root span's baggage scope; hand the token back to :func:`pop_baggage_scope`.
91
+
92
+ Setting the variable to its own current value is what mints the token — there is no other
93
+ way to say "restore whatever was here", including the case where nothing was ever set. It
94
+ is safe because no baggage dict is ever mutated in place (see this module's docstring).
95
+ """
96
+ return _baggage.set(_baggage.get())
97
+
98
+
99
+ def pop_baggage_scope(token: contextvars.Token[dict[str, object]]) -> None:
100
+ """Close a root span's scope: restore baggage, discard any adopted trace context.
101
+
102
+ The two are deliberately **asymmetric**. Baggage is *restored*, so a process-level default
103
+ set before any span outlives the traces that run under it while a request's own keys do not
104
+ (SPEC-024 FR-001). The adopted context is *cleared*, because it is a one-shot handoff to the
105
+ trace it was adopted for: restoring it would leave the next invocation still joining the
106
+ previous caller's trace, which is the defect this scope exists to close (FR-002). A caller
107
+ who opens no span at all clears both with :func:`reset_context`.
108
+
109
+ Total — never raises, because a decorated function must not fail on the way out (arch §4).
110
+
111
+ One caveat the ``contextvars`` model makes unavoidable: both writes land in the context the
112
+ root span's ``finally`` runs in. An adoption made *outside* a span that then runs in a child
113
+ context — any ``asyncio.Task``, including the one ``asyncio.run`` creates — is cleared in
114
+ the copy, not in the parent that holds it. :func:`continue_trace` is documented to be called on
115
+ the entry point's first line, which is inside the span and unaffected; a caller who adopts
116
+ before dispatching across a task boundary clears it with :func:`reset_context`.
117
+ """
118
+ # Cleared first, and deliberately ahead of anything that could fail: a stale trace id puts
119
+ # *wrong* data in the log stream, while a missed baggage restore only leaves a stale field.
120
+ _adopted.set(None)
121
+ try:
122
+ _baggage.reset(token)
123
+ except Exception:
124
+ # `reset` rejects a token minted in another context (``ValueError``, reachable when a
125
+ # span body hands work to another thread) and one already used (``RuntimeError``).
126
+ # Setting the captured value directly is equivalent: this context never had the scope
127
+ # pushed onto it, so there is nothing else to unwind. Broad because the alternative is
128
+ # raising from a `finally` and replacing the caller's own exception (arch §4).
129
+ # `old_value` is ``Token.MISSING`` when the variable was unset at capture, and absent
130
+ # entirely if this was never a Token; neither can have come from `set_baggage`, so both
131
+ # land on empty rather than poisoning baggage with whatever was passed.
132
+ old = getattr(token, "old_value", None)
133
+ _baggage.set(old if isinstance(old, dict) else {})
134
+
135
+
84
136
  # -- adopted inbound context (SPEC-014) --------------------------------------------------
85
137
 
86
138
 
@@ -114,6 +114,12 @@ def continue_trace(
114
114
  Call it on the **first line** of the entry point: a child span that already finished has
115
115
  been handed to the worker and can no longer be rewritten.
116
116
 
117
+ The adoption is **consumed by that one root span** and does not survive it (SPEC-024): the
118
+ next root span opened with no fresh call of its own starts a new trace, which is what stops
119
+ a warm container from logging every later invocation into the first caller's trace. So a
120
+ batch that fans out to several *sibling* root spans needs one call per item — or, better, a
121
+ single ``@trace`` entry point so the items are nested spans of one trace.
122
+
117
123
  ``baggage`` is a W3C ``baggage`` header merged into the current context. It succeeds or
118
124
  fails independently of the trace context.
119
125
 
@@ -298,8 +304,11 @@ def trace(
298
304
 
299
305
  @functools.wraps(fn)
300
306
  async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
307
+ # Before _open_span, which is what makes the new span current (SPEC-024).
308
+ is_root = context.current_span() is None
301
309
  span = _open_span(name or fn.__qualname__, defaults)
302
310
  token = context.push_span(span)
311
+ scope = context.push_baggage_scope() if is_root else None
303
312
  try:
304
313
  result = await fn(*args, **kwargs)
305
314
  _close_span(span, "ok", None)
@@ -312,13 +321,18 @@ def trace(
312
321
  raise
313
322
  finally:
314
323
  context.pop_span(token)
324
+ if scope is not None:
325
+ context.pop_baggage_scope(scope)
315
326
 
316
327
  return cast("F", async_wrapper)
317
328
 
318
329
  @functools.wraps(fn)
319
330
  def wrapper(*args: Any, **kwargs: Any) -> Any:
331
+ # Before _open_span, which is what makes the new span current (SPEC-024).
332
+ is_root = context.current_span() is None
320
333
  span = _open_span(name or fn.__qualname__, defaults)
321
334
  token = context.push_span(span)
335
+ scope = context.push_baggage_scope() if is_root else None
322
336
  try:
323
337
  result = fn(*args, **kwargs)
324
338
  _close_span(span, "ok", None)
@@ -329,6 +343,8 @@ def trace(
329
343
  raise
330
344
  finally:
331
345
  context.pop_span(token)
346
+ if scope is not None:
347
+ context.pop_baggage_scope(scope)
332
348
 
333
349
  return cast("F", wrapper)
334
350