log-foundry 0.5.1.dev7__tar.gz → 0.5.1.dev9__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.5.1.dev7 → log_foundry-0.5.1.dev9}/PKG-INFO +20 -2
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/README.md +19 -1
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/pyproject.toml +1 -1
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/__init__.py +24 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/decorator.py +14 -1
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/multi.py +26 -1
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/worker.py +49 -4
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/LICENSE +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/api.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/config.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/console.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/context.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/model.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.5.1.dev7 → log_foundry-0.5.1.dev9}/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.5.1.
|
|
3
|
+
Version: 0.5.1.dev9
|
|
4
4
|
Summary: Generate logs for your console and JSON events for downstream consumption.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -395,6 +395,9 @@ A few conventions hold across every sink below:
|
|
|
395
395
|
open for you to manage.
|
|
396
396
|
- **Never crashes the app.** A failing sink is retried with backoff and then counted (`.failed`,
|
|
397
397
|
`.dropped_oversized`, …) rather than raised — a broken destination degrades logging, nothing more.
|
|
398
|
+
The one deliberate exception is a `MultiSink` whose children *all* failed: it re-raises so the
|
|
399
|
+
worker's retry engages, since nothing was delivered and there are no duplicates to risk. That
|
|
400
|
+
still doesn't reach your code — the worker is what catches it.
|
|
398
401
|
|
|
399
402
|
#### Built-in, zero-dependency
|
|
400
403
|
|
|
@@ -612,7 +615,22 @@ background worker via a fast, non-blocking submit — your function returns with
|
|
|
612
615
|
the sink. The worker batches events (by count and time), emits them on its own thread, retries
|
|
613
616
|
a failing sink with backoff, and applies backpressure so a slow or down sink can never block or
|
|
614
617
|
back-pressure the app: when its bounded queue is full it drops the newest submissions and counts
|
|
615
|
-
them
|
|
618
|
+
them rather than stalling.
|
|
619
|
+
|
|
620
|
+
Those losses are deliberate, so the library gives you a way to notice them. `log_foundry.health()`
|
|
621
|
+
returns a snapshot of the worker's counters:
|
|
622
|
+
|
|
623
|
+
```python
|
|
624
|
+
h = log_foundry.health()
|
|
625
|
+
if h.dropped or h.failed_batches:
|
|
626
|
+
... # logs were silently lost — worth an alert
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
`dropped` counts submissions discarded because the queue filled; `failed_batches` counts batches
|
|
630
|
+
abandoned after the retry budget was spent. Overflow also warns on stderr — on the first drop and
|
|
631
|
+
every thousandth after it, since overflow is a high-rate condition and a line per drop would be its
|
|
632
|
+
own outage. A process that has never logged has no worker, and asking after its health does not
|
|
633
|
+
create one.
|
|
616
634
|
|
|
617
635
|
Because delivery is asynchronous, drain before the process exits. There are two drains, and
|
|
618
636
|
which one you want depends on whether the process is about to end:
|
|
@@ -359,6 +359,9 @@ A few conventions hold across every sink below:
|
|
|
359
359
|
open for you to manage.
|
|
360
360
|
- **Never crashes the app.** A failing sink is retried with backoff and then counted (`.failed`,
|
|
361
361
|
`.dropped_oversized`, …) rather than raised — a broken destination degrades logging, nothing more.
|
|
362
|
+
The one deliberate exception is a `MultiSink` whose children *all* failed: it re-raises so the
|
|
363
|
+
worker's retry engages, since nothing was delivered and there are no duplicates to risk. That
|
|
364
|
+
still doesn't reach your code — the worker is what catches it.
|
|
362
365
|
|
|
363
366
|
#### Built-in, zero-dependency
|
|
364
367
|
|
|
@@ -576,7 +579,22 @@ background worker via a fast, non-blocking submit — your function returns with
|
|
|
576
579
|
the sink. The worker batches events (by count and time), emits them on its own thread, retries
|
|
577
580
|
a failing sink with backoff, and applies backpressure so a slow or down sink can never block or
|
|
578
581
|
back-pressure the app: when its bounded queue is full it drops the newest submissions and counts
|
|
579
|
-
them
|
|
582
|
+
them rather than stalling.
|
|
583
|
+
|
|
584
|
+
Those losses are deliberate, so the library gives you a way to notice them. `log_foundry.health()`
|
|
585
|
+
returns a snapshot of the worker's counters:
|
|
586
|
+
|
|
587
|
+
```python
|
|
588
|
+
h = log_foundry.health()
|
|
589
|
+
if h.dropped or h.failed_batches:
|
|
590
|
+
... # logs were silently lost — worth an alert
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
`dropped` counts submissions discarded because the queue filled; `failed_batches` counts batches
|
|
594
|
+
abandoned after the retry budget was spent. Overflow also warns on stderr — on the first drop and
|
|
595
|
+
every thousandth after it, since overflow is a high-rate condition and a line per drop would be its
|
|
596
|
+
own outage. A process that has never logged has no worker, and asking after its health does not
|
|
597
|
+
create one.
|
|
580
598
|
|
|
581
599
|
Because delivery is asynchronous, drain before the process exits. There are two drains, and
|
|
582
600
|
which one you want depends on whether the process is about to end:
|
|
@@ -18,6 +18,7 @@ from log_foundry.context import (
|
|
|
18
18
|
current_traceparent,
|
|
19
19
|
)
|
|
20
20
|
from log_foundry.decorator import continue_trace, trace
|
|
21
|
+
from log_foundry.worker import Health
|
|
21
22
|
|
|
22
23
|
try:
|
|
23
24
|
# Distribution name ("log-foundry") differs from the import name ("log_foundry").
|
|
@@ -49,6 +50,27 @@ def flush(timeout: float | None = 5.0) -> bool:
|
|
|
49
50
|
return _flush_worker(timeout)
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
def health() -> Health:
|
|
54
|
+
"""Snapshot the background worker's delivery counters (SPEC-017 FR-005). Never raises.
|
|
55
|
+
|
|
56
|
+
Returns ``queued`` / ``dropped`` / ``failed_batches``. A non-zero ``dropped`` means the queue
|
|
57
|
+
filled and submissions were discarded to keep your code non-blocking; a non-zero
|
|
58
|
+
``failed_batches`` means a sink stayed broken through the whole retry budget. Both are
|
|
59
|
+
losses the library absorbs on purpose, and this is how you notice them::
|
|
60
|
+
|
|
61
|
+
h = log_foundry.health()
|
|
62
|
+
if h.dropped or h.failed_batches:
|
|
63
|
+
... # raise an alert; logs were silently lost
|
|
64
|
+
|
|
65
|
+
A process that has never logged has no worker, and asking after its health does not create
|
|
66
|
+
one — the snapshot is simply zeroed. Valid after :func:`shutdown`, which leaves the final
|
|
67
|
+
counters readable.
|
|
68
|
+
"""
|
|
69
|
+
from log_foundry.decorator import _worker_health
|
|
70
|
+
|
|
71
|
+
return _worker_health()
|
|
72
|
+
|
|
73
|
+
|
|
52
74
|
def shutdown() -> None:
|
|
53
75
|
"""Flush buffered events and close the sink, blocking until drained. Idempotent.
|
|
54
76
|
|
|
@@ -80,5 +102,7 @@ __all__ = [
|
|
|
80
102
|
"current_baggage_header",
|
|
81
103
|
"flush",
|
|
82
104
|
"shutdown",
|
|
105
|
+
"health",
|
|
106
|
+
"Health",
|
|
83
107
|
"__version__",
|
|
84
108
|
]
|
|
@@ -40,7 +40,7 @@ from log_foundry.ids import (
|
|
|
40
40
|
parse_traceparent,
|
|
41
41
|
)
|
|
42
42
|
from log_foundry.model import Span, backfill_baggage, end_event, start_event
|
|
43
|
-
from log_foundry.worker import Worker
|
|
43
|
+
from log_foundry.worker import Health, Worker
|
|
44
44
|
|
|
45
45
|
__all__ = ["trace", "continue_trace"]
|
|
46
46
|
|
|
@@ -235,6 +235,19 @@ def _flush_worker(timeout: float | None = 5.0) -> bool:
|
|
|
235
235
|
return False
|
|
236
236
|
|
|
237
237
|
|
|
238
|
+
def _worker_health() -> Health:
|
|
239
|
+
"""Snapshot the process worker's counters, or zeros if none was ever created.
|
|
240
|
+
|
|
241
|
+
Backs :func:`log_foundry.health` (SPEC-017 FR-005). Like :func:`_flush_worker` this
|
|
242
|
+
deliberately does *not* call :func:`_get_worker`: starting a thread and registering an
|
|
243
|
+
``atexit`` drain in order to report three zeros would be pure cost.
|
|
244
|
+
"""
|
|
245
|
+
worker = _worker
|
|
246
|
+
if worker is None:
|
|
247
|
+
return Health(queued=0, dropped=0, failed_batches=0)
|
|
248
|
+
return worker.health()
|
|
249
|
+
|
|
250
|
+
|
|
238
251
|
def _flush(span: Span) -> None:
|
|
239
252
|
"""Hand the finished span's events to the background worker — non-blocking (FR-001).
|
|
240
253
|
|
|
@@ -6,6 +6,12 @@ whose ``emit`` (or ``close``) raises is isolated — the failure is counted on `
|
|
|
6
6
|
logged to stderr, and its siblings still run — so one broken destination never fails the whole
|
|
7
7
|
fan-out or the worker's retry. This mirrors the worker's own survive-a-sink-failure isolation
|
|
8
8
|
boundary (arch §9; best-practices §7 sanctions the broad catch here).
|
|
9
|
+
|
|
10
|
+
The one exception is **total** failure: if every child raised, ``emit`` re-raises rather than
|
|
11
|
+
reporting success, so the worker's retry engages (SPEC-017 FR-004). Swallowing there meant a
|
|
12
|
+
``MultiSink`` whose destinations were all down reported healthy on every batch and the loss was
|
|
13
|
+
invisible. ``close()`` keeps the unconditional isolate-and-continue behaviour — a failed close
|
|
14
|
+
has nothing to retry.
|
|
9
15
|
"""
|
|
10
16
|
|
|
11
17
|
from __future__ import annotations
|
|
@@ -29,16 +35,35 @@ class MultiSink:
|
|
|
29
35
|
self.failed = 0
|
|
30
36
|
|
|
31
37
|
def emit(self, batch: list[dict[str, object]]) -> None:
|
|
32
|
-
"""Forward ``batch`` to every child in construction order, isolating failures (FR-002).
|
|
38
|
+
"""Forward ``batch`` to every child in construction order, isolating failures (FR-002).
|
|
39
|
+
|
|
40
|
+
When **every** child failed, re-raise the first child's exception so the worker's bounded
|
|
41
|
+
retry sees the total loss (SPEC-017 FR-004). Partial success stays isolated: a retry there
|
|
42
|
+
would re-deliver the batch to the children that already took it, and duplicates are worse
|
|
43
|
+
than the one failure already counted on ``failed`` and written to stderr. Total failure
|
|
44
|
+
delivered nothing, so it has no duplicates to create and is the one case worth retrying.
|
|
45
|
+
"""
|
|
46
|
+
first_error: Exception | None = None
|
|
47
|
+
delivered = 0
|
|
33
48
|
for sink in self._sinks:
|
|
34
49
|
try:
|
|
35
50
|
sink.emit(batch)
|
|
36
51
|
except Exception as err: # isolation boundary: one child must not fail the rest
|
|
37
52
|
self.failed += 1
|
|
53
|
+
if first_error is None:
|
|
54
|
+
first_error = err
|
|
38
55
|
sys.stderr.write(
|
|
39
56
|
f"log-foundry: MultiSink child {type(sink).__name__}.emit "
|
|
40
57
|
f"failed and was skipped: {err!r}\n"
|
|
41
58
|
)
|
|
59
|
+
else:
|
|
60
|
+
delivered += 1
|
|
61
|
+
if delivered == 0 and first_error is not None:
|
|
62
|
+
# Raised outside any ``except`` block, so ``__context__`` is untouched and the
|
|
63
|
+
# exception propagates by identity with its original traceback. The ``is not None``
|
|
64
|
+
# is also what keeps an empty ``MultiSink`` a no-op rather than a raise — otherwise a
|
|
65
|
+
# misconfigured fan-out with no children would retry every batch to exhaustion.
|
|
66
|
+
raise first_error
|
|
42
67
|
|
|
43
68
|
def close(self) -> None:
|
|
44
69
|
"""Close every child, isolating a failing child so the rest still close (FR-002)."""
|
|
@@ -23,16 +23,36 @@ import queue
|
|
|
23
23
|
import sys
|
|
24
24
|
import threading
|
|
25
25
|
import time
|
|
26
|
-
from typing import cast
|
|
26
|
+
from typing import NamedTuple, cast
|
|
27
27
|
|
|
28
28
|
from log_foundry.sinks.base import Sink
|
|
29
29
|
|
|
30
|
-
__all__ = ["Worker"]
|
|
30
|
+
__all__ = ["Health", "Worker"]
|
|
31
31
|
|
|
32
32
|
# Sentinel enqueued by shutdown() to wake a worker blocked in queue.get() so it stops promptly
|
|
33
33
|
# instead of waiting out the flush_interval. It is never emitted.
|
|
34
34
|
_SHUTDOWN = object()
|
|
35
35
|
|
|
36
|
+
# Queue overflow is a high-rate condition by nature: a line per dropped submission would be its
|
|
37
|
+
# own outage. Warn on the first drop, then every this-many-th (SPEC-017 FR-005).
|
|
38
|
+
_DROP_WARN_EVERY = 1000
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Health(NamedTuple):
|
|
42
|
+
"""A point-in-time snapshot of the worker's delivery counters (SPEC-017 FR-005).
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
queued: Submissions currently buffered. Approximate by nature — it is read without
|
|
46
|
+
stopping the world, and briefly counts the internal flush/shutdown markers
|
|
47
|
+
alongside real submissions.
|
|
48
|
+
dropped: Submissions discarded because the queue was full (backpressure).
|
|
49
|
+
failed_batches: Batches abandoned after the retry budget was spent.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
queued: int
|
|
53
|
+
dropped: int
|
|
54
|
+
failed_batches: int
|
|
55
|
+
|
|
36
56
|
|
|
37
57
|
class _FlushMarker:
|
|
38
58
|
"""A drain request travelling the queue in FIFO order (SPEC-013 FR-002).
|
|
@@ -87,13 +107,35 @@ class Worker:
|
|
|
87
107
|
|
|
88
108
|
Enqueues via ``put_nowait`` and returns immediately without touching the sink. When the
|
|
89
109
|
queue is full, drops this submission (drop-newest) and counts it in ``dropped`` rather
|
|
90
|
-
than blocking the caller (FR-001, FR-004).
|
|
110
|
+
than blocking the caller (FR-001, FR-004), warning on a throttle (SPEC-017 FR-005).
|
|
91
111
|
"""
|
|
92
112
|
try:
|
|
93
113
|
self._queue.put_nowait(events)
|
|
94
114
|
except queue.Full:
|
|
95
115
|
with self._lock:
|
|
96
116
|
self.dropped += 1
|
|
117
|
+
total = self.dropped # read under the lock: every value is produced exactly once
|
|
118
|
+
# Written outside the lock deliberately. stderr can block on a slow reader, and
|
|
119
|
+
# ``_lock`` also guards flush()/shutdown()'s once-only flag — holding it across a
|
|
120
|
+
# blocking write would let a wedged console stall the drain path, not just other
|
|
121
|
+
# submitters. Lines may therefore interleave out of order under concurrency; the
|
|
122
|
+
# counts they carry are still exact.
|
|
123
|
+
if total == 1 or total % _DROP_WARN_EVERY == 0:
|
|
124
|
+
sys.stderr.write(
|
|
125
|
+
f"log-foundry: log queue full, dropped {total} submission(s) so far\n"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def health(self) -> Health:
|
|
129
|
+
"""Snapshot the delivery counters (SPEC-017 FR-005). Never raises.
|
|
130
|
+
|
|
131
|
+
Valid after :meth:`shutdown` — the counters are plain integers that outlive the thread,
|
|
132
|
+
and the final drain consumes the queue, so ``queued`` reads 0 rather than a stale marker.
|
|
133
|
+
"""
|
|
134
|
+
with self._lock:
|
|
135
|
+
dropped, failed_batches = self.dropped, self.failed_batches
|
|
136
|
+
return Health(
|
|
137
|
+
queued=self._queue.qsize(), dropped=dropped, failed_batches=failed_batches
|
|
138
|
+
)
|
|
97
139
|
|
|
98
140
|
def flush(self, timeout: float | None = 5.0) -> bool:
|
|
99
141
|
"""Drain everything submitted before this call through the sink, without stopping.
|
|
@@ -221,7 +263,10 @@ class Worker:
|
|
|
221
263
|
return
|
|
222
264
|
except Exception: # noqa: BLE001 — any sink failure must not kill the worker thread
|
|
223
265
|
if attempt >= self.max_retries:
|
|
224
|
-
|
|
266
|
+
# Under the lock so a concurrent health() sees a coherent snapshot rather
|
|
267
|
+
# than a half-updated pair. No deadlock: shutdown() releases before join().
|
|
268
|
+
with self._lock:
|
|
269
|
+
self.failed_batches += 1
|
|
225
270
|
sys.stderr.write(
|
|
226
271
|
f"log-foundry: abandoned a batch of {len(batch)} event(s) after "
|
|
227
272
|
f"{self.max_retries + 1} failed emit attempts\n"
|
|
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
|
|
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
|