log-foundry 0.10.2.dev8__tar.gz → 0.10.2.dev10__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 (54) hide show
  1. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/PKG-INFO +1 -1
  2. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/pyproject.toml +1 -1
  3. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/_diag.py +6 -0
  4. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/api.py +31 -12
  5. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/worker.py +19 -1
  6. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/LICENSE +0 -0
  7. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/README.md +0 -0
  8. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/__init__.py +0 -0
  9. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/config.py +0 -0
  10. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/console.py +0 -0
  11. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/context.py +0 -0
  12. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/decorator.py +0 -0
  13. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/ids.py +0 -0
  14. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/model.py +0 -0
  15. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/py.typed +0 -0
  16. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sanitize.py +0 -0
  17. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/__init__.py +0 -0
  18. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/_batch.py +0 -0
  19. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/_chunk.py +0 -0
  20. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/_socket.py +0 -0
  21. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/_time.py +0 -0
  22. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/base.py +0 -0
  23. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/callback.py +0 -0
  24. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/clickhouse.py +0 -0
  25. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/datadog.py +0 -0
  26. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/elasticsearch.py +0 -0
  27. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/eventhubs.py +0 -0
  28. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/file.py +0 -0
  29. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/filtering.py +0 -0
  30. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/firehose.py +0 -0
  31. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/honeycomb.py +0 -0
  32. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/http.py +0 -0
  33. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/kafka.py +0 -0
  34. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/kinesis.py +0 -0
  35. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/logging_sink.py +0 -0
  36. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/logstash.py +0 -0
  37. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/loki.py +0 -0
  38. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/mongodb.py +0 -0
  39. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/multi.py +0 -0
  40. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/nats.py +0 -0
  41. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/newrelic.py +0 -0
  42. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/postgres.py +0 -0
  43. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/pubsub.py +0 -0
  44. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/rabbitmq.py +0 -0
  45. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/redis.py +0 -0
  46. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/sentry.py +0 -0
  47. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/sns.py +0 -0
  48. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/splunk.py +0 -0
  49. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/sqlite.py +0 -0
  50. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/sqs.py +0 -0
  51. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/stdout.py +0 -0
  52. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/syslog.py +0 -0
  53. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/transform.py +0 -0
  54. {log_foundry-0.10.2.dev8 → log_foundry-0.10.2.dev10}/src/log_foundry/sinks/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: log-foundry
3
- Version: 0.10.2.dev8
3
+ Version: 0.10.2.dev10
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.dev8"
23
+ version = "0.10.2.dev10"
24
24
 
25
25
  [project.optional-dependencies]
26
26
  aws = ["boto3>=1.43.61"] # SQSSink, SNSSink, KinesisSink, FirehoseSink
@@ -19,6 +19,12 @@ Two rules, both load-bearing:
19
19
  as everywhere else in this library: a ``KeyboardInterrupt`` landing mid-write is the operator's
20
20
  intent, not a stream fault to swallow.
21
21
 
22
+ **This module must import nothing from its own package.** ``decorator``, ``api`` and ``worker``
23
+ all reach it with ``from log_foundry import _diag`` at module scope, which executes while the
24
+ package is still partially initialised and resolves only because ``_diag`` is a leaf — it needs
25
+ ``sys`` and nothing else. Give it an intra-package import and every module that is imported
26
+ *first* in a fresh interpreter starts failing.
27
+
22
28
  SPEC-029 takes ownership of this module and moves the ``repr(exception)`` sink sites onto it.
23
29
  """
24
30
 
@@ -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
- orphan = Span(
55
- trace_id=new_trace_id(),
56
- span_id=new_span_id(),
57
- parent_span_id=None,
58
- name=message,
59
- start_ts=0.0,
60
- )
61
- event = build_event(orphan, level, message, fields=fields, baggage=baggage)
62
- _ensure_sink().emit([event])
63
- if echo:
64
- _console.write(event)
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:
@@ -25,6 +25,8 @@ import threading
25
25
  import time
26
26
  from typing import TYPE_CHECKING, NamedTuple, cast
27
27
 
28
+ from log_foundry import _diag
29
+
28
30
  if TYPE_CHECKING:
29
31
  from log_foundry.sinks.base import Sink
30
32
 
@@ -244,6 +246,17 @@ class Worker:
244
246
 
245
247
  Idempotent: a second call is a no-op (FR-005). Registered via ``atexit`` by the
246
248
  decorator's lazy worker so a program that logs and exits immediately still flushes.
249
+
250
+ **Total** (SPEC-025 FR-004). The close was unguarded, so a sink whose ``close()`` failed
251
+ raised out of ``shutdown()`` — and out of the ``atexit`` handler, where CPython printed
252
+ "Exception ignored in atexit callback" with a full traceback carrying the exception's
253
+ message, which arch §6 keeps out of anything the library says about itself.
254
+
255
+ The once-only flag deliberately stays *ahead* of the close rather than moving after it.
256
+ Re-running a drain is not safe, and a second ``shutdown()`` retrying a close that already
257
+ failed would call ``close()`` twice on a sink that may have partially released its
258
+ resources. Idempotence is preserved; what changes is that the failure is announced rather
259
+ than swallowed by the flag.
247
260
  """
248
261
  with self._lock:
249
262
  if self._shutdown_done:
@@ -255,7 +268,12 @@ class Worker:
255
268
  except queue.Full:
256
269
  pass
257
270
  self._thread.join()
258
- self.sink.close()
271
+ try:
272
+ self.sink.close()
273
+ except Exception as exc:
274
+ # After the join, so everything queued has already been drained and emitted: what is
275
+ # lost here is the sink's own cleanup, not events.
276
+ _diag.absorbed("closing the sink", exc, "it may still hold its resources")
259
277
 
260
278
  # -- worker thread ------------------------------------------------------------------
261
279