log-foundry 0.10.2.dev112__tar.gz → 0.10.2.dev114__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.dev112 → log_foundry-0.10.2.dev114}/PKG-INFO +2 -2
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/README.md +1 -1
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/worker.py +83 -5
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/LICENSE +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/_diag.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/_fork.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/_lifecycle.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/decorator.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/results.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/_retry.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/memory.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/null.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/src/log_foundry/sinks/transform.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.dev114
|
|
4
4
|
Summary: Generate logs for your console and JSON events for downstream consumption.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -1208,7 +1208,7 @@ Falsy carries a `reason` saying which of these happened, because they need diffe
|
|
|
1208
1208
|
| `"retired"` | `shutdown()` was already called. Your lifecycle is wrong, not the sink. |
|
|
1209
1209
|
| `"thread-died"` | The drain thread is gone; see `health().stopped_reason`. |
|
|
1210
1210
|
| `"queue-full"` | Backpressure — the queue could not even accept the marker. |
|
|
1211
|
-
| `"abandoned"` |
|
|
1211
|
+
| `"abandoned"` | The drain that would have carried those events did not confirm delivery of them. A broken destination is one cause, but not the only one — an expiring bounded `shutdown()` answers waiting calls pessimistically, and the drain may still deliver afterwards. So this is not by itself evidence that anything was lost: `health()` is what says whether it was. |
|
|
1212
1212
|
| `"sink-flush"` | Everything queued reached the sink, but the sink's own `flush()` raised — a client-side buffer that did not go out. Distinct from `"abandoned"`: the library delivered, the sink did not. |
|
|
1213
1213
|
|
|
1214
1214
|
```python
|
|
@@ -1157,7 +1157,7 @@ Falsy carries a `reason` saying which of these happened, because they need diffe
|
|
|
1157
1157
|
| `"retired"` | `shutdown()` was already called. Your lifecycle is wrong, not the sink. |
|
|
1158
1158
|
| `"thread-died"` | The drain thread is gone; see `health().stopped_reason`. |
|
|
1159
1159
|
| `"queue-full"` | Backpressure — the queue could not even accept the marker. |
|
|
1160
|
-
| `"abandoned"` |
|
|
1160
|
+
| `"abandoned"` | The drain that would have carried those events did not confirm delivery of them. A broken destination is one cause, but not the only one — an expiring bounded `shutdown()` answers waiting calls pessimistically, and the drain may still deliver afterwards. So this is not by itself evidence that anything was lost: `health()` is what says whether it was. |
|
|
1161
1161
|
| `"sink-flush"` | Everything queued reached the sink, but the sink's own `flush()` raised — a client-side buffer that did not go out. Distinct from `"abandoned"`: the library delivered, the sink did not. |
|
|
1162
1162
|
|
|
1163
1163
|
```python
|
|
@@ -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.dev114"
|
|
78
78
|
|
|
79
79
|
[project.urls]
|
|
80
80
|
Homepage = "https://github.com/agriffi10/log-forge"
|
|
@@ -270,9 +270,15 @@ class Worker:
|
|
|
270
270
|
mechanics only, and knows nothing about spans or context.
|
|
271
271
|
"""
|
|
272
272
|
|
|
273
|
-
_FORK_SKIP = ("_unclosed_swaps",)
|
|
273
|
+
_FORK_SKIP = ("_unclosed_swaps", "_taken_markers")
|
|
274
274
|
"""Attribute names ``_fork``'s repair walk must not read or descend into (SPEC-050 FR-004).
|
|
275
275
|
|
|
276
|
+
``_taken_markers`` names ``flush()`` callers **in the parent**, whose ``Event`` the walk would
|
|
277
|
+
otherwise replace on an object nothing in the child will ever wait on. The skip stops that
|
|
278
|
+
work and :meth:`_reinit_after_fork` drops the references, because a skip alone leaves them
|
|
279
|
+
held — measured, a child inherited the parent's in-flight marker permanently and kept it
|
|
280
|
+
through fifty further flushes of its own.
|
|
281
|
+
|
|
276
282
|
``_unclosed_swaps`` holds sinks this process has **stopped** delivering to, which is exactly
|
|
277
283
|
the shape ``_fork._SKIP_ATTRIBUTE`` describes: bookkeeping that pins objects is not live state
|
|
278
284
|
to repair. Without the opt-out the walk reaches a superseded sink, replaces its locks — merely
|
|
@@ -345,6 +351,7 @@ class Worker:
|
|
|
345
351
|
self._shutdown_done = False
|
|
346
352
|
self._sink_closed = sink_released
|
|
347
353
|
self._closing: threading.Event | None = None
|
|
354
|
+
self._taken_markers: list[_FlushMarker] = []
|
|
348
355
|
self._unclosed_swaps: list[Sink] = []
|
|
349
356
|
self._lock = threading.Lock()
|
|
350
357
|
self._offer_stop_signal()
|
|
@@ -385,6 +392,9 @@ class Worker:
|
|
|
385
392
|
release at every child exit. Emptying is what "a child inherits no promise" means for the
|
|
386
393
|
record as well as for the close.
|
|
387
394
|
|
|
395
|
+
``_taken_markers`` and ``_unclosed_swaps`` are emptied with it: a child inherits no
|
|
396
|
+
``flush()`` caller to answer and stranded no sink, so both are references it can only hold.
|
|
397
|
+
|
|
388
398
|
``_closing`` is emptied unconditionally, on both branches (SPEC-050 FR-002). It names a
|
|
389
399
|
close running on a thread that did not survive the fork, so the child inherits a promise
|
|
390
400
|
nothing can keep: ``_fork._fresh_primitive`` carries an ``Event``'s set state across, so
|
|
@@ -447,6 +457,7 @@ class Worker:
|
|
|
447
457
|
self.incomplete_swaps = 0
|
|
448
458
|
self.stopped_reason = None
|
|
449
459
|
self._closing = None
|
|
460
|
+
self._taken_markers = []
|
|
450
461
|
self._unclosed_swaps = []
|
|
451
462
|
if not resume:
|
|
452
463
|
self._drain_finished.set()
|
|
@@ -1342,8 +1353,67 @@ class Worker:
|
|
|
1342
1353
|
finally:
|
|
1343
1354
|
self._release_waiters()
|
|
1344
1355
|
|
|
1356
|
+
def _take_marker(self, marker: _FlushMarker) -> None:
|
|
1357
|
+
"""Records a marker the drain thread has taken out of the queue (SPEC-050 FR-001).
|
|
1358
|
+
|
|
1359
|
+
:meth:`_release_waiters` answers markers by reading ``self._queue.queue``, so it can only
|
|
1360
|
+
reach one that is still *in* the queue. A marker the drain has already dequeued is held in
|
|
1361
|
+
that thread's local while it emits — and if the sink's ``emit`` never returns, nothing
|
|
1362
|
+
answers it and a ``flush(timeout=None)`` waits forever, which is the defect FR-001 exists
|
|
1363
|
+
to remove. Registering it here is what puts it back within reach.
|
|
1364
|
+
|
|
1365
|
+
Args:
|
|
1366
|
+
marker: The marker this thread is about to work on.
|
|
1367
|
+
|
|
1368
|
+
Returns:
|
|
1369
|
+
None.
|
|
1370
|
+
|
|
1371
|
+
Raises:
|
|
1372
|
+
None.
|
|
1373
|
+
"""
|
|
1374
|
+
with self._lock:
|
|
1375
|
+
self._taken_markers.append(marker)
|
|
1376
|
+
settled = self._drain_settled.is_set()
|
|
1377
|
+
if settled:
|
|
1378
|
+
marker.event.set()
|
|
1379
|
+
|
|
1380
|
+
def _release_marker(self, marker: _FlushMarker) -> None:
|
|
1381
|
+
"""Drops a marker the drain thread has finished with, before it is answered.
|
|
1382
|
+
|
|
1383
|
+
**It runs after ``event.set()``, and that order is load-bearing** — the reverse survives
|
|
1384
|
+
the suite, which is this repo's evidence that nothing covers it rather than that it is
|
|
1385
|
+
safe. An earlier draft of this docstring read the green as proof of equivalence and said
|
|
1386
|
+
so; it is not. Deregistering first leaves a window in which the marker is in neither the
|
|
1387
|
+
queue nor the record, so an async ``BaseException`` landing there — the one ``_run``
|
|
1388
|
+
catches, on this thread — strands a ``flush(timeout=None)`` that ``_run``'s own closing
|
|
1389
|
+
sweep can no longer reach. Answering first inverts the failure into a leaked list entry on
|
|
1390
|
+
a worker that is already dead. This is the reasoning :meth:`_final_drain` already applies
|
|
1391
|
+
to its own ``finally``.
|
|
1392
|
+
|
|
1393
|
+
Args:
|
|
1394
|
+
marker: The marker this thread has finished with.
|
|
1395
|
+
|
|
1396
|
+
Returns:
|
|
1397
|
+
None.
|
|
1398
|
+
|
|
1399
|
+
Raises:
|
|
1400
|
+
None.
|
|
1401
|
+
"""
|
|
1402
|
+
with self._lock:
|
|
1403
|
+
self._taken_markers = [m for m in self._taken_markers if m is not marker]
|
|
1404
|
+
|
|
1345
1405
|
def _release_waiters(self) -> None:
|
|
1346
|
-
"""Answers every ``flush()`` marker
|
|
1406
|
+
"""Answers every outstanding ``flush()`` marker, so no caller waits out its timeout.
|
|
1407
|
+
|
|
1408
|
+
**Two populations, and the second is not optional** (SPEC-050 FR-001). A marker still in
|
|
1409
|
+
the queue is read from it; a marker the drain thread has already **taken** is read from
|
|
1410
|
+
:attr:`_taken_markers`, because between dequeuing one and returning from ``sink.emit`` the
|
|
1411
|
+
drain holds it in a local where a queue read cannot see it. Answering only the first is
|
|
1412
|
+
what the audit prescribed and it does not cover the audit's own probe. The taken markers
|
|
1413
|
+
are answered **before** the queue read and outside its ``try``: that read reaches into
|
|
1414
|
+
``Queue``'s privates, a risk ``architecture.md`` §13 accepts on the understanding that a
|
|
1415
|
+
CPython change costs *timed-out* waiters, and letting the new mechanism inherit it would
|
|
1416
|
+
upgrade that cost to waiters that never return at all.
|
|
1347
1417
|
|
|
1348
1418
|
A ``BaseException`` from the main loop skips :meth:`_final_drain` entirely, which is
|
|
1349
1419
|
where queued markers are normally answered, leaving a waiter to sit for its full
|
|
@@ -1352,7 +1422,7 @@ class Worker:
|
|
|
1352
1422
|
``health().queued`` and the terminal line report; each keeps its pessimistic
|
|
1353
1423
|
``delivered``, which is the truth here.
|
|
1354
1424
|
|
|
1355
|
-
It is called from
|
|
1425
|
+
It is called from three places. The terminal-failure path is the original one. The clean
|
|
1356
1426
|
:meth:`shutdown` path was added because that same enqueue-after-the-drain race happens
|
|
1357
1427
|
there too, and hurts more: measured stranding a marker in 13 of 400 shutdowns raced
|
|
1358
1428
|
against a ``flush()`` under load, where the caller sat out its whole timeout — and
|
|
@@ -1382,10 +1452,14 @@ class Worker:
|
|
|
1382
1452
|
Raises:
|
|
1383
1453
|
None. This runs after the record and the stderr line, neither of which may be lost.
|
|
1384
1454
|
"""
|
|
1455
|
+
with self._lock:
|
|
1456
|
+
taken = list(self._taken_markers)
|
|
1457
|
+
for marker in taken:
|
|
1458
|
+
marker.event.set()
|
|
1385
1459
|
try:
|
|
1386
1460
|
with self._queue.mutex:
|
|
1387
|
-
|
|
1388
|
-
for marker in
|
|
1461
|
+
queued = [i for i in self._queue.queue if isinstance(i, _FlushMarker)]
|
|
1462
|
+
for marker in queued:
|
|
1389
1463
|
marker.event.set()
|
|
1390
1464
|
except Exception:
|
|
1391
1465
|
pass
|
|
@@ -1463,12 +1537,14 @@ class Worker:
|
|
|
1463
1537
|
except queue.Empty:
|
|
1464
1538
|
item = None
|
|
1465
1539
|
if isinstance(item, _FlushMarker):
|
|
1540
|
+
self._take_marker(item)
|
|
1466
1541
|
try:
|
|
1467
1542
|
self._emit_pending(pending)
|
|
1468
1543
|
item.delivered = self._nothing_lost_since(item)
|
|
1469
1544
|
finally:
|
|
1470
1545
|
last_flush = time.monotonic()
|
|
1471
1546
|
item.event.set()
|
|
1547
|
+
self._release_marker(item)
|
|
1472
1548
|
continue
|
|
1473
1549
|
if item is _SHUTDOWN:
|
|
1474
1550
|
break
|
|
@@ -1546,6 +1622,7 @@ class Worker:
|
|
|
1546
1622
|
break
|
|
1547
1623
|
if isinstance(item, _FlushMarker):
|
|
1548
1624
|
markers.append(item)
|
|
1625
|
+
self._take_marker(item)
|
|
1549
1626
|
continue
|
|
1550
1627
|
if item is not None and item is not _SHUTDOWN:
|
|
1551
1628
|
pending.append(cast("list[dict[str, object]]", item))
|
|
@@ -1556,6 +1633,7 @@ class Worker:
|
|
|
1556
1633
|
finally:
|
|
1557
1634
|
for marker in markers:
|
|
1558
1635
|
marker.event.set()
|
|
1636
|
+
self._release_marker(marker)
|
|
1559
1637
|
|
|
1560
1638
|
def _emit(self, event_lists: list[list[dict[str, object]]]) -> None:
|
|
1561
1639
|
"""Flattens queued per-span event-lists into one batch and emits it, retrying.
|
|
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
|
{log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/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
|
{log_foundry-0.10.2.dev112 → log_foundry-0.10.2.dev114}/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
|