log-foundry 0.10.2.dev12__tar.gz → 0.10.2.dev13__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.dev12 → log_foundry-0.10.2.dev13}/PKG-INFO +1 -1
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/_diag.py +4 -1
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/decorator.py +5 -22
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/worker.py +23 -29
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/LICENSE +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/README.md +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.10.2.dev12 → log_foundry-0.10.2.dev13}/src/log_foundry/sinks/util.py +0 -0
|
@@ -94,7 +94,10 @@ def lost(what: str, count: int, detail: str = "") -> None:
|
|
|
94
94
|
Args:
|
|
95
95
|
what: Singular noun for the unit lost — ``"event"``, ``"message"``, ``"row"``. Rendered
|
|
96
96
|
with the corpus-wide ``(s)`` suffix, so it must be a literal, never a runtime value.
|
|
97
|
-
count: How many
|
|
97
|
+
count: How many this line reports as lost — normally the increment the caller's counter
|
|
98
|
+
has just taken. A **throttled** site instead passes its running total, because a line
|
|
99
|
+
written on every thousandth drop that said "lost 1" would read as one loss rather than
|
|
100
|
+
a thousand; such a site says so in ``detail``. Recorded before this call either way.
|
|
98
101
|
detail: Optional circumstances — the sink's class name, the attempt count, an ``errno``,
|
|
99
102
|
an exception *type*. Escaped and truncated to ``_MAX_DETAIL``.
|
|
100
103
|
"""
|
|
@@ -24,7 +24,6 @@ from __future__ import annotations
|
|
|
24
24
|
import asyncio
|
|
25
25
|
import atexit
|
|
26
26
|
import functools
|
|
27
|
-
import sys
|
|
28
27
|
import threading
|
|
29
28
|
from collections.abc import Callable
|
|
30
29
|
from time import monotonic
|
|
@@ -47,9 +46,6 @@ if TYPE_CHECKING:
|
|
|
47
46
|
|
|
48
47
|
__all__ = ["continue_trace", "trace"]
|
|
49
48
|
|
|
50
|
-
# Bound on how much of a rejected inbound value is echoed into a stderr warning.
|
|
51
|
-
_MAX_REJECTED_ECHO = 64
|
|
52
|
-
|
|
53
49
|
# One background worker per process (SPEC-004), created lazily from the configured sink on the
|
|
54
50
|
# first flush. The double-checked lock makes concurrent first-flushes create exactly one.
|
|
55
51
|
_worker: Worker | None = None
|
|
@@ -86,19 +82,6 @@ def _open_span(name: str, defaults: dict[str, object] | None) -> Span:
|
|
|
86
82
|
return span
|
|
87
83
|
|
|
88
84
|
|
|
89
|
-
def _warn_rejected(reason: str, value: object) -> None:
|
|
90
|
-
"""Report a rejected inbound context on stderr, as ``worker`` and ``SQSSink`` do.
|
|
91
|
-
|
|
92
|
-
The offending value is echoed only as a **bounded ``repr``**. Unbounded is a log-injection
|
|
93
|
-
surface — the value is attacker-controllable, and `repr` additionally escapes newlines and
|
|
94
|
-
control characters so it cannot forge a second log line in an operator's console.
|
|
95
|
-
"""
|
|
96
|
-
shown = repr(value)
|
|
97
|
-
if len(shown) > _MAX_REJECTED_ECHO:
|
|
98
|
-
shown = shown[:_MAX_REJECTED_ECHO] + "…"
|
|
99
|
-
sys.stderr.write(f"log-foundry: ignoring inbound trace context ({reason}): {shown}\n")
|
|
100
|
-
|
|
101
|
-
|
|
102
85
|
def continue_trace(
|
|
103
86
|
traceparent: str | None = None,
|
|
104
87
|
*,
|
|
@@ -144,19 +127,19 @@ def continue_trace(
|
|
|
144
127
|
if traceparent is not None:
|
|
145
128
|
if trace_id is not None or parent_span_id is not None:
|
|
146
129
|
# A programming error, not bad input: the caller supplied the same thing twice.
|
|
147
|
-
|
|
130
|
+
_diag.rejected("both traceparent and explicit ids given; traceparent wins", traceparent)
|
|
148
131
|
parsed = parse_traceparent(traceparent)
|
|
149
132
|
if parsed is None:
|
|
150
|
-
|
|
133
|
+
_diag.rejected("unparseable traceparent", traceparent)
|
|
151
134
|
else:
|
|
152
135
|
adopted = parsed
|
|
153
136
|
elif trace_id is not None:
|
|
154
137
|
if not is_valid_trace_id(trace_id):
|
|
155
|
-
|
|
138
|
+
_diag.rejected("invalid trace_id", trace_id)
|
|
156
139
|
elif parent_span_id is not None and not is_valid_span_id(parent_span_id):
|
|
157
140
|
# Drop just the parent and join as another root rather than reject the whole
|
|
158
141
|
# context: being in the right trace without a parent beats being in a fresh one.
|
|
159
|
-
|
|
142
|
+
_diag.rejected("invalid parent_span_id; joining as a root", parent_span_id)
|
|
160
143
|
adopted = (trace_id, None)
|
|
161
144
|
else:
|
|
162
145
|
# parent_span_id may legitimately be omitted — a consumer that knows the trace but
|
|
@@ -175,7 +158,7 @@ def continue_trace(
|
|
|
175
158
|
if parsed_baggage is None:
|
|
176
159
|
# Deliberately independent of the trace context above: losing correlating fields is
|
|
177
160
|
# bad, and losing the trace join because one field was malformed is worse.
|
|
178
|
-
|
|
161
|
+
_diag.rejected("unusable baggage header", baggage)
|
|
179
162
|
else:
|
|
180
163
|
context.set_baggage(**parsed_baggage)
|
|
181
164
|
|
|
@@ -20,7 +20,6 @@ nothing about spans or context (the same dumbness that makes sinks swappable).
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import queue
|
|
23
|
-
import sys
|
|
24
23
|
import threading
|
|
25
24
|
import time
|
|
26
25
|
from typing import TYPE_CHECKING, NamedTuple, cast
|
|
@@ -162,15 +161,7 @@ class Worker:
|
|
|
162
161
|
# submitters. Lines may therefore interleave out of order under concurrency; the
|
|
163
162
|
# counts they carry are still exact.
|
|
164
163
|
if total == 1 or total % _DROP_WARN_EVERY == 0:
|
|
165
|
-
|
|
166
|
-
sys.stderr.write(
|
|
167
|
-
f"log-foundry: log queue full, dropped {total} submission(s) so far\n"
|
|
168
|
-
)
|
|
169
|
-
except Exception: # submit() runs on the *caller's* thread, so an
|
|
170
|
-
# unwritable stderr (closed fd, broken pipe, daemonized process) would raise
|
|
171
|
-
# straight into the app. A diagnostic about dropped logs must never itself be
|
|
172
|
-
# the reason a decorated function fails. The counter is already recorded.
|
|
173
|
-
pass
|
|
164
|
+
_diag.lost("submission", total, "log queue full; count is cumulative")
|
|
174
165
|
|
|
175
166
|
def health(self) -> Health:
|
|
176
167
|
"""Snapshot the delivery counters (SPEC-017 FR-005, SPEC-019 FR-003). Never raises.
|
|
@@ -323,36 +314,36 @@ class Worker:
|
|
|
323
314
|
|
|
324
315
|
Recording precedes announcing: stderr may be closed or wedged, and unlike the overflow
|
|
325
316
|
warning this line is written exactly once and cannot be re-emitted later, so the record
|
|
326
|
-
must not be able to ride on it. The exception's *type* is reported and its message is
|
|
327
|
-
|
|
328
|
-
|
|
317
|
+
must not be able to ride on it. The exception's *type* is reported and its message is not
|
|
318
|
+
— the rule ``_diag`` now applies to every line the library writes (SPEC-029), and the
|
|
319
|
+
reason this site had it first.
|
|
329
320
|
|
|
330
|
-
The
|
|
331
|
-
|
|
332
|
-
|
|
321
|
+
The announcement is an :func:`~log_foundry._diag.absorbed`, not a fourth kind of line: the
|
|
322
|
+
thread's death *is* an exception this method caught and did not propagate, and what it
|
|
323
|
+
cost belongs in the detail. The count reports what was *in hand* and what was still
|
|
324
|
+
*queued behind it* (SPEC-021 FR-002). Held alone under-reads the loss: nothing will drain
|
|
325
|
+
the queue either, so an operator reading "1 undrained event-list(s)" could conclude far
|
|
326
|
+
less was lost than was.
|
|
333
327
|
|
|
334
328
|
The queued figure is "items", not "event-lists", and says so: like ``Health.queued`` it is
|
|
335
329
|
read without stopping the world, so it counts any internal flush/shutdown marker sitting
|
|
336
330
|
alongside real submissions, and a producer thread can add to the queue between the death
|
|
337
331
|
and the read. It is a floor on what was lost, which is the useful direction.
|
|
338
332
|
"""
|
|
339
|
-
name = type(exc).__name__
|
|
340
333
|
with self._lock:
|
|
341
|
-
self.stopped_reason =
|
|
334
|
+
self.stopped_reason = type(exc).__name__
|
|
342
335
|
try:
|
|
343
336
|
# In its own guard, and after the record: ``qsize()`` is not guaranteed on every
|
|
344
337
|
# platform's queue, and a diagnostic must not be the reason the diagnosis is lost.
|
|
345
338
|
queued: object = self._queue.qsize()
|
|
346
339
|
except Exception:
|
|
347
340
|
queued = "?"
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
except Exception: # best-effort: the record above is what an operator reads.
|
|
355
|
-
pass
|
|
341
|
+
_diag.absorbed(
|
|
342
|
+
"draining the log queue",
|
|
343
|
+
exc,
|
|
344
|
+
f"worker thread stopped; {undrained} undrained event-list(s) held and {queued} "
|
|
345
|
+
f"queued item(s) undelivered, nothing further will be delivered",
|
|
346
|
+
)
|
|
356
347
|
|
|
357
348
|
def _drain(self, pending: list[list[dict[str, object]]]) -> None:
|
|
358
349
|
"""The drain loop proper. ``pending`` is owned by :meth:`_run`, which reports its size.
|
|
@@ -477,9 +468,12 @@ class Worker:
|
|
|
477
468
|
# than a half-updated pair. No deadlock: shutdown() releases before join().
|
|
478
469
|
with self._lock:
|
|
479
470
|
self.failed_batches += 1
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
471
|
+
# Through ``_diag`` so the write is guarded: unguarded, a broken stderr raised
|
|
472
|
+
# out of here, through ``_drain``, into ``_run``'s handler, and the drain
|
|
473
|
+
# thread died for good — a diagnostic about one lost batch costing every batch
|
|
474
|
+
# after it (SPEC-029 FR-003).
|
|
475
|
+
_diag.lost(
|
|
476
|
+
"event", len(batch), f"batch abandoned after {retries + 1} emit attempts"
|
|
483
477
|
)
|
|
484
478
|
return
|
|
485
479
|
# Backoff between attempts; _stop.wait returns at once during shutdown, so a
|
|
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.dev12 → log_foundry-0.10.2.dev13}/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
|