log-foundry 0.10.2.dev10__tar.gz → 0.10.2.dev12__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.dev10 → log_foundry-0.10.2.dev12}/PKG-INFO +1 -1
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/pyproject.toml +1 -1
- log_foundry-0.10.2.dev12/src/log_foundry/_diag.py +212 -0
- log_foundry-0.10.2.dev10/src/log_foundry/_diag.py +0 -59
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/LICENSE +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/README.md +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/decorator.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/sinks/util.py +0 -0
- {log_foundry-0.10.2.dev10 → log_foundry-0.10.2.dev12}/src/log_foundry/worker.py +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""The library's own diagnostic channel — one line on stderr, for what it lost or absorbed.
|
|
2
|
+
|
|
3
|
+
This library *is* the logger, so it cannot report its own faults through itself: an event
|
|
4
|
+
describing a broken sink would be handed to that same broken sink. Stderr is the channel of last
|
|
5
|
+
resort, and **every** line the library writes about itself goes through here, so the wording and
|
|
6
|
+
the safety rules are decided once rather than remembered at twenty-odd call sites — which is how
|
|
7
|
+
twelve of them came to disagree with the other eight (SPEC-029).
|
|
8
|
+
|
|
9
|
+
Three rules, all load-bearing:
|
|
10
|
+
|
|
11
|
+
* **The exception's type, never its message.** A message can carry a value from the event that
|
|
12
|
+
provoked it, and arch §6 keeps user data out of anything the library emits about itself — the
|
|
13
|
+
same rule SPEC-019 applies to ``Health.stopped_reason`` and ``sanitize`` applies by refusing
|
|
14
|
+
``repr(value)``. ``PostgresSink`` is the sharpest case: ``_row`` binds the whole
|
|
15
|
+
``json.dumps(event)`` as a statement parameter, and a psycopg error repr routinely reprints the
|
|
16
|
+
failing statement *and its parameters* — so a naive diagnostic for a failed insert would reprint
|
|
17
|
+
the event, PII included, into a stream nobody was asked to secure. A type name tells a
|
|
18
|
+
``ConnectionError`` from a ``TypeError``, which is what the reader needs. Where a bare type is
|
|
19
|
+
genuinely not diagnosable — an ``OSError`` alone does not say "refused" from "host unknown" —
|
|
20
|
+
the caller passes a ``detail`` built from values the *library* controls: an ``errno`` (see
|
|
21
|
+
:func:`errno_of`), an HTTP status, an attempt count, a sink's class name. Never the exception's
|
|
22
|
+
text. Any ``detail`` is escaped and bounded regardless, so a value that reached one could not
|
|
23
|
+
forge a second line in an operator's console.
|
|
24
|
+
* **No stream fault escapes.** These writers are called from ``except`` and ``finally`` blocks
|
|
25
|
+
whose entire purpose is to stop an exception reaching the caller, and from the worker thread
|
|
26
|
+
where an escaping exception ends delivery for good. A failure here — a closed stderr at
|
|
27
|
+
interpreter shutdown, a stream that rejects the write, ``sys.stderr`` set to ``None`` — must not
|
|
28
|
+
become the failure those guards existed to prevent. A ``BaseException`` still passes through, as
|
|
29
|
+
everywhere else in this library: a ``KeyboardInterrupt`` landing mid-write is the operator's
|
|
30
|
+
intent, not a stream fault to swallow.
|
|
31
|
+
* **Record first, announce second.** A counter moves *before* its line is attempted, at every call
|
|
32
|
+
site. The write is best-effort by construction; the counter that ``health()`` reports is not, and
|
|
33
|
+
must not be able to ride on it.
|
|
34
|
+
|
|
35
|
+
Stderr stays the channel: routing through the ``logging`` module would make the library's own
|
|
36
|
+
failure reports depend on machinery that may be the thing failing, and ``LoggingSink`` already
|
|
37
|
+
exists for the opposite direction. These lines are for humans — the structured stream is the sink.
|
|
38
|
+
|
|
39
|
+
**This module must import nothing from its own package.** ``decorator``, ``api``, ``worker`` and
|
|
40
|
+
every sink reach it with ``from log_foundry import _diag`` at module scope, which executes while
|
|
41
|
+
the package is still partially initialised and resolves only because ``_diag`` is a leaf — it needs
|
|
42
|
+
``sys`` and nothing else. Give it an intra-package import and every module that is imported *first*
|
|
43
|
+
in a fresh interpreter starts failing.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from __future__ import annotations
|
|
47
|
+
|
|
48
|
+
import sys
|
|
49
|
+
|
|
50
|
+
__all__ = ["absorbed", "errno_of", "lost", "rejected"]
|
|
51
|
+
|
|
52
|
+
# Bound on any caller-supplied detail string, applied after escaping so it bounds what is
|
|
53
|
+
# *written*. Generous enough for an attempt count plus a sink name plus an errno; far short of a
|
|
54
|
+
# payload.
|
|
55
|
+
_MAX_DETAIL = 200
|
|
56
|
+
|
|
57
|
+
# Bound on how much of a rejected inbound value is echoed (SPEC-014). Tighter than _MAX_DETAIL:
|
|
58
|
+
# the value is attacker-controllable, and its shape is the diagnosis, not its contents.
|
|
59
|
+
_MAX_REJECTED_ECHO = 64
|
|
60
|
+
|
|
61
|
+
_ESCAPES = {"\t": "\\t", "\n": "\\n", "\r": "\\r"}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def absorbed(where: str, exc: BaseException, detail: str = "") -> None:
|
|
65
|
+
"""Report a failure the library swallowed rather than propagated.
|
|
66
|
+
|
|
67
|
+
Never raises on a stream fault; a ``BaseException`` from the write still propagates.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
where: What was being attempted, as a participle phrase that reads after "while" —
|
|
71
|
+
``"closing a span"``, ``"emitting an orphan log"``. A literal, never a runtime value.
|
|
72
|
+
exc: The absorbed exception. Only ``type(exc).__name__`` is written.
|
|
73
|
+
detail: Optional consequence for the reader, appended after a semicolon — most usefully
|
|
74
|
+
what was lost, since an absorbed failure is invisible apart from this line. Escaped
|
|
75
|
+
and truncated to ``_MAX_DETAIL``; must not be derived from ``exc``'s text.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
sys.stderr.write(
|
|
79
|
+
f"log-foundry: absorbed a failure while {_escape(where)} "
|
|
80
|
+
f"({_escape(type(exc).__name__)}){_suffix(detail)}\n"
|
|
81
|
+
)
|
|
82
|
+
except Exception:
|
|
83
|
+
# The channel of last resort has no fallback of its own. Reporting is best-effort by
|
|
84
|
+
# construction: losing the line is bad, raising from the guard that exists to protect
|
|
85
|
+
# the caller's exception would be worse (arch §4).
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def lost(what: str, count: int, detail: str = "") -> None:
|
|
90
|
+
"""Report a counted loss — events, messages, batches or rows that will not be delivered.
|
|
91
|
+
|
|
92
|
+
Never raises on a stream fault; a ``BaseException`` from the write still propagates.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
what: Singular noun for the unit lost — ``"event"``, ``"message"``, ``"row"``. Rendered
|
|
96
|
+
with the corpus-wide ``(s)`` suffix, so it must be a literal, never a runtime value.
|
|
97
|
+
count: How many were lost. The caller's counter has already moved by this much.
|
|
98
|
+
detail: Optional circumstances — the sink's class name, the attempt count, an ``errno``,
|
|
99
|
+
an exception *type*. Escaped and truncated to ``_MAX_DETAIL``.
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
sys.stderr.write(f"log-foundry: lost {count} {_escape(what)}(s){_suffix(detail)}\n")
|
|
103
|
+
except Exception:
|
|
104
|
+
pass # best-effort, as above; the counter this line describes is already recorded.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def rejected(reason: str, value: object) -> None:
|
|
108
|
+
"""Report an inbound trace context the library refused to adopt (SPEC-014).
|
|
109
|
+
|
|
110
|
+
The offending value is echoed as a **bounded ``repr``** — the one place in the library where a
|
|
111
|
+
``repr`` is correct, because the input is an inbound *header* rather than an exception, and its
|
|
112
|
+
exact shape is what makes a rejection diagnosable. Unbounded it would be a log-injection
|
|
113
|
+
surface: the value is attacker-controllable.
|
|
114
|
+
|
|
115
|
+
The ``repr`` is escaped afterwards even though ``repr`` of a ``str`` is already printable
|
|
116
|
+
throughout, which makes this a no-op for every value the call sites actually pass. It is not a
|
|
117
|
+
no-op in general: ``repr`` runs ``__repr__``, which is user code free to *return* a raw newline
|
|
118
|
+
no matter what the built-in reprs would have done. Escaping the result is the difference
|
|
119
|
+
between "the built-ins happen to escape" and "this cannot forge a line".
|
|
120
|
+
|
|
121
|
+
Never raises on a stream fault; a ``BaseException`` from the write still propagates.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
reason: Why it was refused — ``"unparseable traceparent"``, ``"invalid trace_id"``. A
|
|
125
|
+
literal, never a runtime value: unlike the echo, it is not bounded.
|
|
126
|
+
value: The refused value, echoed as a ``repr`` bounded to ``_MAX_REJECTED_ECHO``.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
shown = _escape(repr(value))
|
|
130
|
+
if len(shown) > _MAX_REJECTED_ECHO:
|
|
131
|
+
shown = shown[:_MAX_REJECTED_ECHO] + "…"
|
|
132
|
+
sys.stderr.write(
|
|
133
|
+
f"log-foundry: ignoring inbound trace context ({_escape(reason)}): {shown}\n"
|
|
134
|
+
)
|
|
135
|
+
except Exception:
|
|
136
|
+
pass # best-effort, as above.
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def errno_of(exc: BaseException) -> str:
|
|
140
|
+
"""Return ``"errno=N"`` for an exception carrying one, else ``""``.
|
|
141
|
+
|
|
142
|
+
The library-controlled detail that makes an ``OSError`` line actionable: "connection refused"
|
|
143
|
+
and "host unknown" are the same type name and are told apart only by the code. An integer from
|
|
144
|
+
the OS is not caller data, which is what makes it safe to write where the message is not.
|
|
145
|
+
|
|
146
|
+
``urllib``'s ``URLError`` carries the underlying socket error on ``.reason`` rather than
|
|
147
|
+
setting its own ``errno``, so that is consulted too. Total: attribute access on an arbitrary
|
|
148
|
+
exception can run a property that raises, and a helper for a diagnostic must not become one.
|
|
149
|
+
|
|
150
|
+
The value is rendered through ``int()`` rather than interpolated as found. ``isinstance(x,
|
|
151
|
+
int)`` admits any *subclass*, and a driver's error code is routinely one — an ``IntEnum`` or a
|
|
152
|
+
bespoke class whose ``__str__`` returns whatever its author chose, which the f-string would
|
|
153
|
+
then have written verbatim. That is the leak this helper exists to be the alternative to.
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
code = getattr(exc, "errno", None)
|
|
157
|
+
if code is None:
|
|
158
|
+
code = getattr(getattr(exc, "reason", None), "errno", None)
|
|
159
|
+
return f"errno={int(code)}" if isinstance(code, int) else ""
|
|
160
|
+
except Exception:
|
|
161
|
+
return ""
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _suffix(detail: str) -> str:
|
|
165
|
+
"""Render a detail as a bounded, escaped ``"; ..."`` suffix, or ``""`` when there is none.
|
|
166
|
+
|
|
167
|
+
Escaping precedes truncation so the bound applies to what is written, and so truncation can
|
|
168
|
+
only ever remove characters from an already-safe string rather than split an escape into
|
|
169
|
+
something that isn't one.
|
|
170
|
+
|
|
171
|
+
Guarded in its own right, and returning ``""`` rather than propagating: this runs inside the
|
|
172
|
+
caller's f-string, so a detail that cannot be rendered would otherwise take the *whole* line
|
|
173
|
+
with it — including the count, which is the part an operator cannot reconstruct.
|
|
174
|
+
"""
|
|
175
|
+
if not detail:
|
|
176
|
+
return ""
|
|
177
|
+
try:
|
|
178
|
+
shown = _escape(detail)
|
|
179
|
+
if len(shown) > _MAX_DETAIL:
|
|
180
|
+
shown = shown[:_MAX_DETAIL] + "…"
|
|
181
|
+
return f"; {shown}"
|
|
182
|
+
except Exception:
|
|
183
|
+
return ""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _escape(text: str) -> str:
|
|
187
|
+
"""Render every non-printable character visibly, so nothing in ``text`` can forge a line.
|
|
188
|
+
|
|
189
|
+
``str.isprintable()`` is the test rather than a table over ``range(0x20)``: it is ``False`` for
|
|
190
|
+
the C0 block *and* for DEL, the C1 block (U+009B is CSI, which a terminal reads as the start of
|
|
191
|
+
an escape sequence), U+0085, U+2028, U+2029, and the bidi format characters. Python's own
|
|
192
|
+
``splitlines()`` breaks on three of those a C0 table misses, so a log shipper or a reader doing
|
|
193
|
+
the obvious thing would see a forged ``log-foundry:`` line that a newline count says is not
|
|
194
|
+
there. Space is printable, so ordinary text is untouched.
|
|
195
|
+
|
|
196
|
+
The whole-string fast path keeps the overwhelmingly common case — a sink name, an attempt
|
|
197
|
+
count, an exception type — to a single C-level call.
|
|
198
|
+
"""
|
|
199
|
+
if text.isprintable():
|
|
200
|
+
return text
|
|
201
|
+
return "".join(char if char.isprintable() else _escaped(char) for char in text)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _escaped(char: str) -> str:
|
|
205
|
+
"""Escape one non-printable character, following Python's own ``repr`` conventions."""
|
|
206
|
+
escape = _ESCAPES.get(char)
|
|
207
|
+
if escape is not None:
|
|
208
|
+
return escape
|
|
209
|
+
code = ord(char)
|
|
210
|
+
if code <= 0xFF:
|
|
211
|
+
return f"\\x{code:02x}"
|
|
212
|
+
return f"\\u{code:04x}" if code <= 0xFFFF else f"\\U{code:08x}"
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"""The library's own diagnostic channel — one line on stderr, for failures it absorbed.
|
|
2
|
-
|
|
3
|
-
This library *is* the logger, so it cannot report its own faults through itself: an event
|
|
4
|
-
describing a broken sink would be handed to that same broken sink. Stderr is the channel of last
|
|
5
|
-
resort, and every site that swallows an exception to keep architecture §4's promise (logging never
|
|
6
|
-
breaks the application) announces it through here, so the wording and the safety rules are decided
|
|
7
|
-
once rather than per call site.
|
|
8
|
-
|
|
9
|
-
Two rules, both load-bearing:
|
|
10
|
-
|
|
11
|
-
* **The exception's type, never its message.** A message can carry a value from the event that
|
|
12
|
-
provoked it, and arch §6 keeps user data out of anything the library emits about itself — the
|
|
13
|
-
same rule SPEC-019 applies to ``Health.stopped_reason``. A type name is enough to tell a
|
|
14
|
-
``ConnectionError`` from a ``TypeError``, which is what the reader needs.
|
|
15
|
-
* **No stream fault escapes.** It is called from ``except`` and ``finally`` blocks whose entire
|
|
16
|
-
purpose is to stop an exception reaching the caller, so a failure here — a closed stderr at
|
|
17
|
-
interpreter shutdown, a stream that rejects the write, ``sys.stderr`` set to ``None`` — must not
|
|
18
|
-
become the exception those guards existed to prevent. A ``BaseException`` still passes through,
|
|
19
|
-
as everywhere else in this library: a ``KeyboardInterrupt`` landing mid-write is the operator's
|
|
20
|
-
intent, not a stream fault to swallow.
|
|
21
|
-
|
|
22
|
-
**This module must import nothing from its own package.** ``decorator``, ``api`` and ``worker``
|
|
23
|
-
all reach it with ``from log_foundry import _diag`` at module scope, which executes while the
|
|
24
|
-
package is still partially initialised and resolves only because ``_diag`` is a leaf — it needs
|
|
25
|
-
``sys`` and nothing else. Give it an intra-package import and every module that is imported
|
|
26
|
-
*first* in a fresh interpreter starts failing.
|
|
27
|
-
|
|
28
|
-
SPEC-029 takes ownership of this module and moves the ``repr(exception)`` sink sites onto it.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
from __future__ import annotations
|
|
32
|
-
|
|
33
|
-
import sys
|
|
34
|
-
|
|
35
|
-
__all__ = ["absorbed"]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def absorbed(where: str, exc: BaseException, detail: str = "") -> None:
|
|
39
|
-
"""Report a failure the library swallowed rather than propagated.
|
|
40
|
-
|
|
41
|
-
Never raises on a stream fault; a ``BaseException`` from the write still propagates.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
where: What was being attempted, as a participle phrase that reads after "while" —
|
|
45
|
-
``"closing a span"``, ``"emitting an orphan log"``.
|
|
46
|
-
exc: The absorbed exception. Only ``type(exc).__name__`` is written.
|
|
47
|
-
detail: Optional consequence for the reader, appended after a semicolon — most usefully
|
|
48
|
-
what was lost, since an absorbed failure is invisible apart from this line.
|
|
49
|
-
"""
|
|
50
|
-
try:
|
|
51
|
-
suffix = f"; {detail}" if detail else ""
|
|
52
|
-
sys.stderr.write(
|
|
53
|
-
f"log-foundry: absorbed a failure while {where} ({type(exc).__name__}){suffix}\n"
|
|
54
|
-
)
|
|
55
|
-
except Exception:
|
|
56
|
-
# The channel of last resort has no fallback of its own. Reporting is best-effort by
|
|
57
|
-
# construction: losing the line is bad, raising from the guard that exists to protect
|
|
58
|
-
# the caller's exception would be worse (arch §4).
|
|
59
|
-
pass
|
|
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.dev10 → log_foundry-0.10.2.dev12}/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
|