log-foundry 0.10.2.dev82__tar.gz → 0.10.2.dev84__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.dev82 → log_foundry-0.10.2.dev84}/PKG-INFO +1 -1
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/_lifecycle.py +152 -9
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/LICENSE +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/README.md +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/_diag.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/_fork.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/decorator.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/results.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/_retry.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/memory.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/null.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.10.2.dev82 → log_foundry-0.10.2.dev84}/src/log_foundry/worker.py +0 -0
|
@@ -1365,6 +1365,92 @@ def _offer_orphan_signal(sink: Sink) -> None:
|
|
|
1365
1365
|
if _closing(sink):
|
|
1366
1366
|
return
|
|
1367
1367
|
offer_stop_signal(sink, _state.refresh_stop_signal())
|
|
1368
|
+
def _close_owed(sink: Sink) -> None:
|
|
1369
|
+
"""Closes one sink the orphan path owed, absorbing whatever the close raised (SPEC-046).
|
|
1370
|
+
|
|
1371
|
+
Factored out so the inline close and the threaded ones are provably the same call, and
|
|
1372
|
+
because a thread body must not raise: an exception escaping one reaches CPython's bootstrap,
|
|
1373
|
+
which prints a traceback carrying the message arch §6 keeps out of anything the library says
|
|
1374
|
+
about itself. It is the same guard :func:`_close_guarded` applies for a swapped-out sink, with
|
|
1375
|
+
the ``_diag`` text of the site it actually is (SPEC-029).
|
|
1376
|
+
|
|
1377
|
+
It records nothing in the closed-sink latch. :func:`_close_orphan_sink` **empties the owed
|
|
1378
|
+
record** under ``_state._lock`` before any close starts, which is what stops two callers
|
|
1379
|
+
performing the same close. The closed-sink latch is a single slot and holds only the last of
|
|
1380
|
+
them — unchanged by SPEC-046, and why a racing emit can still re-arm one of the others is
|
|
1381
|
+
recorded in ``architecture.md`` §13.
|
|
1382
|
+
|
|
1383
|
+
Args:
|
|
1384
|
+
sink: The sink to close.
|
|
1385
|
+
|
|
1386
|
+
Returns:
|
|
1387
|
+
None.
|
|
1388
|
+
|
|
1389
|
+
Raises:
|
|
1390
|
+
None. ``Exception``, never ``BaseException`` — the SPEC-025 line. On the calling thread that
|
|
1391
|
+
keeps a ``KeyboardInterrupt`` or ``SystemExit`` reaching the caller as that decision
|
|
1392
|
+
requires; on a fan-out thread it cannot, because there is no caller to reach. CPython's
|
|
1393
|
+
``threading.excepthook`` announces an interrupt itself and discards a ``SystemExit``, so
|
|
1394
|
+
one raised by a threaded close is reported by the runtime rather than by this library and
|
|
1395
|
+
does not propagate. That is :func:`_close_guarded`'s shipped behaviour too; it is stated
|
|
1396
|
+
here rather than left to be discovered.
|
|
1397
|
+
"""
|
|
1398
|
+
try:
|
|
1399
|
+
release(sink)
|
|
1400
|
+
except Exception as exc:
|
|
1401
|
+
_diag.absorbed("closing the sink", exc, "it may still hold its resources")
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
def _live_config_sink() -> Sink | None:
|
|
1405
|
+
"""Returns the configured sink, for callers that must not import config at module scope.
|
|
1406
|
+
|
|
1407
|
+
Args:
|
|
1408
|
+
None.
|
|
1409
|
+
|
|
1410
|
+
Returns:
|
|
1411
|
+
The sink the config names, or None when none is set.
|
|
1412
|
+
|
|
1413
|
+
Raises:
|
|
1414
|
+
None.
|
|
1415
|
+
"""
|
|
1416
|
+
from log_foundry.config import _live_config
|
|
1417
|
+
|
|
1418
|
+
return _live_config().sink
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
def _inline_close_choice(owed: list[Sink]) -> Sink:
|
|
1422
|
+
"""Picks the owed sink whose close stays on the calling thread (SPEC-046 FR-001).
|
|
1423
|
+
|
|
1424
|
+
The **configured** sink where it is among those owed, and otherwise the most recently armed.
|
|
1425
|
+
The config is the authority for which sink is being delivered to, and keeping that one inline
|
|
1426
|
+
is what preserves SPEC-030's decision that ``shutdown()``'s own close stays inline. The
|
|
1427
|
+
fallback is what keeps the single-owed-sink case free of a thread it does not need.
|
|
1428
|
+
|
|
1429
|
+
Membership is by **identity**, never ``in``. ``list.__contains__`` is ``x is e or x == e``, so
|
|
1430
|
+
a sink with a value ``__eq__`` — a dataclass, say, which ``Sink`` permits and no shipped sink
|
|
1431
|
+
happens to be — would match an object the record never armed: the inline close would then run
|
|
1432
|
+
against a sink that was never latched, and every genuinely owed sink would go to a thread.
|
|
1433
|
+
|
|
1434
|
+
This deliberately does not copy :func:`_delivering_to_an_inherited_sink`, which takes the
|
|
1435
|
+
record's last entry and whose own docstring says neither end of the record is authoritative
|
|
1436
|
+
for "installed".
|
|
1437
|
+
|
|
1438
|
+
Args:
|
|
1439
|
+
owed: The sinks owed a close, in arming order and never empty.
|
|
1440
|
+
|
|
1441
|
+
Returns:
|
|
1442
|
+
The one to close on the calling thread.
|
|
1443
|
+
|
|
1444
|
+
Raises:
|
|
1445
|
+
None.
|
|
1446
|
+
"""
|
|
1447
|
+
configured = _live_config_sink()
|
|
1448
|
+
for sink in owed:
|
|
1449
|
+
if sink is configured:
|
|
1450
|
+
return sink
|
|
1451
|
+
return owed[-1]
|
|
1452
|
+
|
|
1453
|
+
|
|
1368
1454
|
def _close_orphan_sink() -> None:
|
|
1369
1455
|
"""Closes a sink only the orphan path ever wrote to, once (SPEC-031 FR-006).
|
|
1370
1456
|
|
|
@@ -1394,6 +1480,41 @@ def _close_orphan_sink() -> None:
|
|
|
1394
1480
|
The once-only flag is set ahead of the close, as ``Worker.shutdown``'s is: a second
|
|
1395
1481
|
``close()`` on a sink that partially released its resources is worse than an unclosed one.
|
|
1396
1482
|
|
|
1483
|
+
**The owed closes run concurrently and every one is joined** (SPEC-046). Draining the record
|
|
1484
|
+
in sequence made this cost one slow close *times* the number owed — measured against
|
|
1485
|
+
``shutdown(timeout=1.0)`` with 2-second closes, one owed sink 2.00 s and four 8.02 s — which
|
|
1486
|
+
is a multiplication SPEC-045 introduced when it made the record a set. One sink closes on the
|
|
1487
|
+
calling thread (:func:`_inline_close_choice` picks which, and why) and the rest get a thread
|
|
1488
|
+
each, so the cost is the slowest rather than the sum.
|
|
1489
|
+
|
|
1490
|
+
**Joined, deliberately not detached.** Routing them through :func:`_start_closer` and
|
|
1491
|
+
:func:`join_closers` is the obvious reuse and it loses data, in two independent ways. The
|
|
1492
|
+
grace is what remains of the shutdown's budget, which a slow inline close can exhaust
|
|
1493
|
+
entirely — measured completing **1 of 4** against a 1.0 s budget. And it caps at
|
|
1494
|
+
:data:`DEFAULT_CLOSER_GRACE` regardless, so even inside a generous budget a sink whose
|
|
1495
|
+
``close()`` outlasts two seconds is abandoned and killed at interpreter exit — measured, a
|
|
1496
|
+
3-second close delivered nothing where it delivers today. It also recharges the double grace SPEC-044 measured, and runs into
|
|
1497
|
+
the §13 entry recording that a daemon close of *this* sink was built and reverted because
|
|
1498
|
+
exit can kill it inside ``SQLiteSink.commit()``. Joining every close avoids all three, and is
|
|
1499
|
+
strictly better than the sequential drain on both axes: the cost falls and the loss stays
|
|
1500
|
+
zero. The threads are daemons only so that one which somehow outlives the join cannot keep the
|
|
1501
|
+
interpreter alive; the join, not the flag, is what guarantees each close completes.
|
|
1502
|
+
|
|
1503
|
+
**The join is in a ``finally``**, so a ``BaseException`` — the ``KeyboardInterrupt`` SPEC-025
|
|
1504
|
+
requires to reach the caller — waits for the started closes before it propagates. Without it,
|
|
1505
|
+
a Ctrl-C during ``shutdown()`` returned with every fan-out close abandoned *mid-write*, where
|
|
1506
|
+
the sequential drain abandoned one and had merely not started the rest: measured 4 killed
|
|
1507
|
+
mid-write against 1, which trades a leaked resource for a corrupt one and is the wrong side
|
|
1508
|
+
of the ordering §13 records for exactly this hazard. The interrupt still reaches the caller;
|
|
1509
|
+
it is delayed by the closes already in flight, which is the same wait the inline close has
|
|
1510
|
+
always imposed.
|
|
1511
|
+
|
|
1512
|
+
A thread that will not start is closed **inline** instead, which is the opposite of
|
|
1513
|
+
:func:`_start_closer`'s refusal and deliberately so: that helper is spending a caller's
|
|
1514
|
+
bounded budget, and falling back to an inline close there would reintroduce the unbounded
|
|
1515
|
+
wait it exists to remove. This path has no budget left to protect — it is the exit — so the
|
|
1516
|
+
choice is between closing inline and never closing at all.
|
|
1517
|
+
|
|
1397
1518
|
Args:
|
|
1398
1519
|
None.
|
|
1399
1520
|
|
|
@@ -1412,11 +1533,32 @@ def _close_orphan_sink() -> None:
|
|
|
1412
1533
|
for sink in owed:
|
|
1413
1534
|
del _state._orphan_owed[id(sink)]
|
|
1414
1535
|
_state._orphan_closed_sink = sink
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1536
|
+
if not owed:
|
|
1537
|
+
return
|
|
1538
|
+
inline = _inline_close_choice(owed)
|
|
1539
|
+
started: list[threading.Thread] = []
|
|
1540
|
+
try:
|
|
1541
|
+
for sink in owed:
|
|
1542
|
+
if sink is inline:
|
|
1543
|
+
continue
|
|
1544
|
+
closer = threading.Thread(
|
|
1545
|
+
target=_close_owed, args=(sink,), name="log-foundry-owed-close", daemon=True
|
|
1546
|
+
)
|
|
1547
|
+
try:
|
|
1548
|
+
closer.start()
|
|
1549
|
+
except Exception as exc:
|
|
1550
|
+
_diag.absorbed(
|
|
1551
|
+
"starting the thread that closes an owed sink",
|
|
1552
|
+
exc,
|
|
1553
|
+
"it is closed inline instead",
|
|
1554
|
+
)
|
|
1555
|
+
_close_owed(sink)
|
|
1556
|
+
else:
|
|
1557
|
+
started.append(closer)
|
|
1558
|
+
_close_owed(inline)
|
|
1559
|
+
finally:
|
|
1560
|
+
for closer in started:
|
|
1561
|
+
closer.join()
|
|
1420
1562
|
def _shutdown_worker(timeout: float | None = DEFAULT_SHUTDOWN_TIMEOUT) -> None:
|
|
1421
1563
|
"""Drains and closes the process worker, or closes an orphan-only sink, backing ``shutdown()``.
|
|
1422
1564
|
|
|
@@ -1571,8 +1713,9 @@ def _swap_sink(new_sink: Sink, timeout: float | None = DEFAULT_SWAP_TIMEOUT) ->
|
|
|
1571
1713
|
closer is not joined: the worker branch returns through :meth:`Worker.swap_sink`'s own
|
|
1572
1714
|
deadline, ``join_closers`` grants the exit grace, and ``health().closing_sinks`` reports it
|
|
1573
1715
|
live. The slot is single, so the worker's sink is latched **last** and wins: it is the case
|
|
1574
|
-
that reproduces without a compound race
|
|
1575
|
-
|
|
1716
|
+
that reproduces without a compound race. The bound it had — the single slot's own — is gone:
|
|
1717
|
+
SPEC-045 replaced that slot with a record of every owed sink, so ``architecture.md`` §13 no
|
|
1718
|
+
longer states one, and the reasoning is in that spec's delivery doc.
|
|
1576
1719
|
|
|
1577
1720
|
Args:
|
|
1578
1721
|
new_sink: The sink already written to the config, to be made the live delivery target.
|
|
@@ -1791,8 +1934,8 @@ def _delivering_to_an_inherited_sink() -> bool:
|
|
|
1791
1934
|
order is ``[live, superseded]``. Neither end of the record is authoritative for "installed" —
|
|
1792
1935
|
arming order is emit order, which is a different question — and the config is. The answer is
|
|
1793
1936
|
therefore unchanged from before SPEC-045 and can still name a superseded sink; that limit is
|
|
1794
|
-
|
|
1795
|
-
changes a documented ``Health`` field on a path
|
|
1937
|
+
an open item in ``architecture.md`` §12 rather than quietly fixed here, because correcting it
|
|
1938
|
+
changes a documented ``Health`` field on a path that spec did not otherwise touch.
|
|
1796
1939
|
|
|
1797
1940
|
With no sink resolved at all there is nothing installed and nothing inherited, so the answer
|
|
1798
1941
|
is ``False`` rather than a guess.
|
|
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.dev82 → log_foundry-0.10.2.dev84}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|