log-foundry 0.10.2.dev60__tar.gz → 0.10.2.dev61__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.dev60 → log_foundry-0.10.2.dev61}/PKG-INFO +1 -1
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/_lifecycle.py +39 -3
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/decorator.py +2 -2
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/filtering.py +6 -2
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/logstash.py +7 -1
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/multi.py +7 -3
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/sentry.py +5 -3
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/transform.py +6 -2
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/worker.py +2 -2
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/LICENSE +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/README.md +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/_diag.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/_fork.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/results.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/_retry.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/memory.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/null.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/src/log_foundry/sinks/syslog.py +0 -0
|
@@ -24,7 +24,42 @@ _closers: list[threading.Thread] = []
|
|
|
24
24
|
_closers_lock = threading.Lock()
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def release(sink: Sink, *, detached: bool = False) -> threading.Thread | None:
|
|
28
|
+
"""Closes a sink on the library's behalf — the one path by which it ever does (SPEC-042 FR-002).
|
|
29
|
+
|
|
30
|
+
Eight sites closed a sink directly before this existed, and guarding only the three the
|
|
31
|
+
lifecycle owns was measurably insufficient: a forked child that wraps an **inherited** sink in
|
|
32
|
+
a ``MultiSink`` of its own reaches the inner sink through the wrapper, so the parent's
|
|
33
|
+
structural sink was closed twice with all three lifecycle sites guarded. Routing every
|
|
34
|
+
library closer through one function is what gives the ownership question one home.
|
|
35
|
+
|
|
36
|
+
**The guard moves here; the error handling does not.** This propagates whatever ``close()``
|
|
37
|
+
raises, because the callers do not agree today and must not be made to: four absorb, under
|
|
38
|
+
three distinct ``_diag`` texts naming the site (SPEC-029), ``MultiSink`` also increments its
|
|
39
|
+
``failed`` counter, and ``FilteringSink``/``TransformSink``/``LogstashSink`` propagate under
|
|
40
|
+
a documented ``Raises:``. Folding the ``try/except`` in here would drop absorbed close
|
|
41
|
+
failures out of ``Health.sink.failed`` — a SPEC-026 regression — and falsify those three.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
sink: The sink to close.
|
|
45
|
+
detached: Whether to close on a daemon thread rather than inline. Detached is for a sink
|
|
46
|
+
the caller has stopped delivering to and must not block on (SPEC-030 FR-003).
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The started closer thread for a detached release, or ``None`` — both when an inline close
|
|
50
|
+
completed and when the platform would not give the process another thread.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
Exception: Whatever an inline ``close()`` raised. A detached release raises nothing: the
|
|
54
|
+
thread body absorbs, since there is no caller left to hand it to.
|
|
55
|
+
"""
|
|
56
|
+
if detached:
|
|
57
|
+
return _start_closer(sink)
|
|
58
|
+
sink.close()
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _start_closer(sink: Sink) -> threading.Thread | None:
|
|
28
63
|
"""Starts a daemon close of a sink no longer being delivered to (SPEC-030 FR-003).
|
|
29
64
|
|
|
30
65
|
The thread is returned rather than joined, so a caller holding a lock can start under it and
|
|
@@ -79,7 +114,8 @@ def _close_guarded(sink: Sink) -> None:
|
|
|
79
114
|
The guard is what makes the thread safe to leave unattended: an exception escaping here
|
|
80
115
|
would reach CPython's thread bootstrap, which prints a full traceback carrying the
|
|
81
116
|
exception's message — the user data arch §6 keeps out of anything the library says about
|
|
82
|
-
itself.
|
|
117
|
+
itself. It goes back through :func:`release` rather than calling ``close()`` itself, so the
|
|
118
|
+
thread body is one of the eight callers rather than a ninth close (SPEC-042 FR-002).
|
|
83
119
|
|
|
84
120
|
Args:
|
|
85
121
|
sink: The sink to close.
|
|
@@ -91,7 +127,7 @@ def _close_guarded(sink: Sink) -> None:
|
|
|
91
127
|
None.
|
|
92
128
|
"""
|
|
93
129
|
try:
|
|
94
|
-
sink
|
|
130
|
+
release(sink)
|
|
95
131
|
except Exception as exc:
|
|
96
132
|
_diag.absorbed("closing a swapped-out sink", exc, "it may still hold its resources")
|
|
97
133
|
|
|
@@ -504,7 +504,7 @@ def _close_orphan_sink() -> None:
|
|
|
504
504
|
_orphan_sink = None
|
|
505
505
|
_orphan_closed_sink = owed
|
|
506
506
|
try:
|
|
507
|
-
|
|
507
|
+
_lifecycle.release(owed)
|
|
508
508
|
except Exception as exc:
|
|
509
509
|
_diag.absorbed("closing the sink", exc, "it may still hold its resources")
|
|
510
510
|
|
|
@@ -637,7 +637,7 @@ def _swap_sink(new_sink: Sink, timeout: float | None = DEFAULT_SWAP_TIMEOUT) ->
|
|
|
637
637
|
_offer_orphan_signal(new_sink)
|
|
638
638
|
if _worker is None or _worker.sink is not old:
|
|
639
639
|
_orphan_closed_sink = old
|
|
640
|
-
closer = _lifecycle.
|
|
640
|
+
closer = _lifecycle.release(old, detached=True)
|
|
641
641
|
if worker is not None:
|
|
642
642
|
try:
|
|
643
643
|
worker_holds_sink = worker.swap_sink(new_sink, timeout)
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from log_foundry import _diag
|
|
7
|
+
from log_foundry import _diag, _lifecycle
|
|
8
8
|
from log_foundry.sinks.base import read_losses
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
@@ -172,6 +172,10 @@ class FilteringSink:
|
|
|
172
172
|
def close(self) -> None:
|
|
173
173
|
"""Closes the inner sink (FR-003).
|
|
174
174
|
|
|
175
|
+
Routed through ``_lifecycle.release`` so a sink this process may not release is refused
|
|
176
|
+
here as it is at the lifecycle's own closers (SPEC-042 FR-002). The propagation is
|
|
177
|
+
unchanged: the helper re-raises, and this method's ``Raises:`` is the reason it does.
|
|
178
|
+
|
|
175
179
|
Args:
|
|
176
180
|
None.
|
|
177
181
|
|
|
@@ -181,4 +185,4 @@ class FilteringSink:
|
|
|
181
185
|
Raises:
|
|
182
186
|
Exception: Whatever the inner sink raises on close.
|
|
183
187
|
"""
|
|
184
|
-
self._inner
|
|
188
|
+
_lifecycle.release(self._inner)
|
|
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
import threading
|
|
10
10
|
|
|
11
|
+
from log_foundry import _lifecycle
|
|
11
12
|
from log_foundry.sinks._socket import DEFAULT_MAX_DATAGRAM_BYTES, SocketTransport
|
|
12
13
|
from log_foundry.sinks.base import SinkLosses
|
|
13
14
|
from log_foundry.sinks.http import HTTPSink
|
|
@@ -124,6 +125,11 @@ class LogstashSink:
|
|
|
124
125
|
def close(self) -> None:
|
|
125
126
|
"""Closes whichever backend is held (FR-005).
|
|
126
127
|
|
|
128
|
+
The two branches are deliberately not symmetric. The HTTP backend is a ``Sink`` and goes
|
|
129
|
+
through ``_lifecycle.release``, so a forked child cannot release one it inherited
|
|
130
|
+
(SPEC-042 FR-002); the socket is a ``SocketTransport`` this sink built and owns
|
|
131
|
+
outright, which no sink-ownership record describes.
|
|
132
|
+
|
|
127
133
|
Args:
|
|
128
134
|
None.
|
|
129
135
|
|
|
@@ -134,7 +140,7 @@ class LogstashSink:
|
|
|
134
140
|
Exception: Whatever the backend raises on close.
|
|
135
141
|
"""
|
|
136
142
|
if self._http is not None:
|
|
137
|
-
self._http
|
|
143
|
+
_lifecycle.release(self._http)
|
|
138
144
|
elif self._socket is not None:
|
|
139
145
|
self._socket.close()
|
|
140
146
|
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import threading
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from log_foundry import _diag
|
|
8
|
+
from log_foundry import _diag, _lifecycle
|
|
9
9
|
from log_foundry.sinks.base import SinkLosses, read_losses
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
@@ -178,7 +178,11 @@ class MultiSink:
|
|
|
178
178
|
"""Closes every child, isolating a failing one so the rest still close (FR-002).
|
|
179
179
|
|
|
180
180
|
Unlike :meth:`emit` this keeps the unconditional isolate-and-continue behaviour even on
|
|
181
|
-
total failure, because a failed close has nothing to retry.
|
|
181
|
+
total failure, because a failed close has nothing to retry. Each child goes through
|
|
182
|
+
``_lifecycle.release`` rather than being closed here, so a child this process may not
|
|
183
|
+
release is refused by the same guard the lifecycle's own closers consult (SPEC-042
|
|
184
|
+
FR-002) — the wrapper route is how a forked child reached a parent's sink with all three
|
|
185
|
+
lifecycle sites already guarded.
|
|
182
186
|
|
|
183
187
|
Args:
|
|
184
188
|
None.
|
|
@@ -191,7 +195,7 @@ class MultiSink:
|
|
|
191
195
|
"""
|
|
192
196
|
for sink in self._sinks:
|
|
193
197
|
try:
|
|
194
|
-
|
|
198
|
+
_lifecycle.release(sink)
|
|
195
199
|
except Exception as err:
|
|
196
200
|
with self._counter_lock:
|
|
197
201
|
self.failed += 1
|
|
@@ -8,7 +8,7 @@ import uuid
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
from urllib.parse import urlparse
|
|
10
10
|
|
|
11
|
-
from log_foundry import _diag
|
|
11
|
+
from log_foundry import _diag, _lifecycle
|
|
12
12
|
from log_foundry.sinks.base import SinkDeliveryError, SinkLosses
|
|
13
13
|
from log_foundry.sinks.http import HTTPSink
|
|
14
14
|
|
|
@@ -128,7 +128,9 @@ class SentrySink:
|
|
|
128
128
|
Idempotent, and it releases nothing — which is why the class docstring's post-close claim
|
|
129
129
|
holds despite this method calling a ``close()``. ``HTTPSink.close`` is a documented no-op,
|
|
130
130
|
since ``urllib`` builds a fresh connection per request; the forward exists so a future
|
|
131
|
-
``HTTPSink`` that *did* hold a pool would be released here rather than leaked.
|
|
131
|
+
``HTTPSink`` that *did* hold a pool would be released here rather than leaked. It goes
|
|
132
|
+
through ``_lifecycle.release`` for that same future: the day the forward releases
|
|
133
|
+
something is the day a forked child must not perform it (SPEC-042 FR-002).
|
|
132
134
|
|
|
133
135
|
Args:
|
|
134
136
|
None.
|
|
@@ -140,7 +142,7 @@ class SentrySink:
|
|
|
140
142
|
None.
|
|
141
143
|
"""
|
|
142
144
|
if self._http is not None:
|
|
143
|
-
self._http
|
|
145
|
+
_lifecycle.release(self._http)
|
|
144
146
|
|
|
145
147
|
@property
|
|
146
148
|
def log_foundry_stop_signal(self) -> threading.Event | None:
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from log_foundry import _diag
|
|
7
|
+
from log_foundry import _diag, _lifecycle
|
|
8
8
|
from log_foundry.sinks.base import read_losses
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
@@ -140,6 +140,10 @@ class TransformSink:
|
|
|
140
140
|
def close(self) -> None:
|
|
141
141
|
"""Closes the inner sink (FR-004).
|
|
142
142
|
|
|
143
|
+
Routed through ``_lifecycle.release`` so a sink this process may not release is refused
|
|
144
|
+
here as it is at the lifecycle's own closers (SPEC-042 FR-002). The propagation is
|
|
145
|
+
unchanged: the helper re-raises, and this method's ``Raises:`` is the reason it does.
|
|
146
|
+
|
|
143
147
|
Args:
|
|
144
148
|
None.
|
|
145
149
|
|
|
@@ -149,4 +153,4 @@ class TransformSink:
|
|
|
149
153
|
Raises:
|
|
150
154
|
Exception: Whatever the inner sink raises on close.
|
|
151
155
|
"""
|
|
152
|
-
self._inner
|
|
156
|
+
_lifecycle.release(self._inner)
|
|
@@ -790,7 +790,7 @@ class Worker:
|
|
|
790
790
|
wait this method exists to remove, in the one situation where the process is
|
|
791
791
|
already under resource pressure.
|
|
792
792
|
"""
|
|
793
|
-
closer = _lifecycle.
|
|
793
|
+
closer = _lifecycle.release(sink, detached=True)
|
|
794
794
|
if closer is not None:
|
|
795
795
|
closer.join(timeout)
|
|
796
796
|
|
|
@@ -1009,7 +1009,7 @@ class Worker:
|
|
|
1009
1009
|
None.
|
|
1010
1010
|
"""
|
|
1011
1011
|
try:
|
|
1012
|
-
self.sink
|
|
1012
|
+
_lifecycle.release(self.sink)
|
|
1013
1013
|
except Exception as exc:
|
|
1014
1014
|
_diag.absorbed("closing the sink", exc, "it may still hold its resources")
|
|
1015
1015
|
|
|
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
|
{log_foundry-0.10.2.dev60 → log_foundry-0.10.2.dev61}/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
|
|
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
|