log-foundry 0.10.2.dev108__tar.gz → 0.10.2.dev110__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.dev108 → log_foundry-0.10.2.dev110}/PKG-INFO +1 -1
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/__init__.py +18 -4
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/config.py +7 -3
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/context.py +8 -5
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/decorator.py +11 -5
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/results.py +1 -1
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/base.py +9 -1
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/datadog.py +9 -4
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/elasticsearch.py +8 -4
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/filtering.py +3 -3
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/honeycomb.py +9 -4
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/http.py +60 -7
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/logstash.py +9 -5
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/loki.py +7 -4
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/newrelic.py +13 -4
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/sentry.py +6 -2
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/splunk.py +7 -4
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/sqs.py +4 -1
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/transform.py +3 -3
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/worker.py +12 -7
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/LICENSE +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/README.md +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/_diag.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/_fork.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/_lifecycle.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/_retry.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/memory.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/null.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/syslog.py +0 -0
|
@@ -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.dev110"
|
|
78
78
|
|
|
79
79
|
[project.urls]
|
|
80
80
|
Homepage = "https://github.com/agriffi10/log-forge"
|
|
@@ -15,8 +15,14 @@ from log_foundry.context import (
|
|
|
15
15
|
)
|
|
16
16
|
from log_foundry.decorator import continue_trace, trace
|
|
17
17
|
from log_foundry.results import ContinueResult, FlushResult
|
|
18
|
-
from log_foundry.sinks.base import
|
|
19
|
-
|
|
18
|
+
from log_foundry.sinks.base import (
|
|
19
|
+
Sink,
|
|
20
|
+
SinkDeliveryError,
|
|
21
|
+
SinkLosses,
|
|
22
|
+
flush_sink,
|
|
23
|
+
read_losses,
|
|
24
|
+
)
|
|
25
|
+
from log_foundry.worker import DEFAULT_SHUTDOWN_TIMEOUT, DEFAULT_SWAP_TIMEOUT, Health
|
|
20
26
|
|
|
21
27
|
try:
|
|
22
28
|
__version__ = _dist_version("log-foundry")
|
|
@@ -95,7 +101,9 @@ def health() -> Health:
|
|
|
95
101
|
|
|
96
102
|
Returns:
|
|
97
103
|
The snapshot: ``queued``, ``dropped``, ``failed_batches``, ``stopped_reason``, ``sink``,
|
|
98
|
-
``retired``, ``submitted_after_shutdown
|
|
104
|
+
``retired``, ``submitted_after_shutdown``, ``incomplete_swaps``, ``closing_sinks``,
|
|
105
|
+
``inherited_sink``, ``orphan_lost`` and ``in_span_lost`` — every field, since a counter
|
|
106
|
+
documented nowhere is a loss nobody looks for. ``retired`` says
|
|
99
107
|
:func:`shutdown` was called, and ``submitted_after_shutdown`` counts events accepted
|
|
100
108
|
afterwards, which are queued where nothing will drain them — non-zero together, that is
|
|
101
109
|
the ``shutdown()``-per-invocation mistake, and the remedy is :func:`flush`.
|
|
@@ -112,7 +120,11 @@ def health() -> Health:
|
|
|
112
120
|
Its ``dropped`` is not the worker's: the worker's is backpressure at the queue, the
|
|
113
121
|
sink's is an event that never reached the wire, and the stderr line names which. Its
|
|
114
122
|
``failed`` is an upper bound on loss rather than a count of it, since a sink that raises
|
|
115
|
-
on total failure counts the attempt and hands the batch back for the worker to retry.
|
|
123
|
+
on total failure counts the attempt and hands the batch back for the worker to retry.
|
|
124
|
+
``inherited_sink`` is a state and not a fault: it says **whether** the sink this process
|
|
125
|
+
last installed for delivery is one it inherited across a ``fork`` and may not release,
|
|
126
|
+
which is what explains a handle still open after :func:`shutdown` (SPEC-042). ``orphan_lost`` and ``in_span_lost``
|
|
127
|
+
are the two terms above that describe no worker at all, and the alert idiom ends on them. A
|
|
116
128
|
process that has never logged has no worker, and asking does not create one — the
|
|
117
129
|
snapshot is simply zeroed, except for ``retired``, which stays truthful even for a
|
|
118
130
|
process that only ever logged outside a span and so built no worker at all (SPEC-031
|
|
@@ -176,6 +188,7 @@ def shutdown(timeout: float | None = DEFAULT_SHUTDOWN_TIMEOUT) -> None:
|
|
|
176
188
|
|
|
177
189
|
__all__ = [
|
|
178
190
|
"DEFAULT_SHUTDOWN_TIMEOUT",
|
|
191
|
+
"DEFAULT_SWAP_TIMEOUT",
|
|
179
192
|
"Config",
|
|
180
193
|
"ContinueResult",
|
|
181
194
|
"FlushResult",
|
|
@@ -193,6 +206,7 @@ __all__ = [
|
|
|
193
206
|
"debug",
|
|
194
207
|
"error",
|
|
195
208
|
"flush",
|
|
209
|
+
"flush_sink",
|
|
196
210
|
"get_baggage",
|
|
197
211
|
"get_config",
|
|
198
212
|
"health",
|
|
@@ -9,10 +9,12 @@ from typing import TYPE_CHECKING
|
|
|
9
9
|
from log_foundry import _lifecycle
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Mapping
|
|
13
|
+
|
|
12
14
|
from log_foundry.sinks.base import Sink
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
@dataclass(frozen=True)
|
|
17
|
+
@dataclass(frozen=True, kw_only=True)
|
|
16
18
|
class Config:
|
|
17
19
|
"""Process-wide settings stamped onto every event and consulted by the pipeline.
|
|
18
20
|
|
|
@@ -92,7 +94,7 @@ def configure(
|
|
|
92
94
|
version: str | None = None,
|
|
93
95
|
env: str | None = None,
|
|
94
96
|
sink: Sink | None = None,
|
|
95
|
-
defaults:
|
|
97
|
+
defaults: Mapping[str, object] | None = None,
|
|
96
98
|
max_value_bytes: int | None = None,
|
|
97
99
|
max_stack_bytes: int | None = None,
|
|
98
100
|
max_keys: int | None = None,
|
|
@@ -147,7 +149,9 @@ def configure(
|
|
|
147
149
|
sink: The destination every event is delivered to. Passed after the first log, it swaps
|
|
148
150
|
the live target as described above rather than only updating what ``get_config()``
|
|
149
151
|
reports.
|
|
150
|
-
defaults: Fields merged into every event at the lowest precedence.
|
|
152
|
+
defaults: Fields merged into every event at the lowest precedence. Any ``Mapping`` is
|
|
153
|
+
accepted and a copy is stored, so a later edit to the caller's own object does not
|
|
154
|
+
reach the config (SPEC-051 FR-002).
|
|
151
155
|
max_value_bytes: Per-value ceiling, in UTF-8 bytes or rendered digits.
|
|
152
156
|
max_stack_bytes: Ceiling for ``error.stack`` alone.
|
|
153
157
|
max_keys: Ceiling on the entries of one mapping or sequence.
|
|
@@ -13,17 +13,20 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
15
|
"current_baggage_header",
|
|
16
|
-
"current_span",
|
|
17
16
|
"current_trace_context",
|
|
18
17
|
"current_traceparent",
|
|
19
18
|
"get_baggage",
|
|
20
|
-
"pop_baggage_scope",
|
|
21
|
-
"pop_span",
|
|
22
|
-
"push_baggage_scope",
|
|
23
|
-
"push_span",
|
|
24
19
|
"reset_context",
|
|
25
20
|
"set_baggage",
|
|
26
21
|
]
|
|
22
|
+
"""Exactly the six names ``log_foundry`` re-exports (SPEC-051 FR-003).
|
|
23
|
+
|
|
24
|
+
The five that left -- ``current_span``, ``push_span``, ``pop_span``, ``push_baggage_scope``
|
|
25
|
+
and ``pop_baggage_scope`` -- are internal and stay importable; what was withdrawn is the
|
|
26
|
+
claim, not the symbol. ``current_span`` is why this matters: it hands back a **mutable**
|
|
27
|
+
:class:`~.model.Span`, which is what :func:`current_trace_context` exists to keep callers
|
|
28
|
+
away from, and a name still in ``__all__`` at ``1.0`` could not be withdrawn afterwards.
|
|
29
|
+
"""
|
|
27
30
|
|
|
28
31
|
_span_stack: contextvars.ContextVar[tuple[Span, ...]] = contextvars.ContextVar(
|
|
29
32
|
"log_foundry_span_stack", default=()
|
|
@@ -22,6 +22,7 @@ from log_foundry.results import ContinueResult
|
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
24
|
import contextvars
|
|
25
|
+
from collections.abc import Mapping
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
__all__ = ["continue_trace", "trace"]
|
|
@@ -631,7 +632,7 @@ def _close_span(span: Span, status: str, exc: BaseException | None) -> None:
|
|
|
631
632
|
def trace(func: F) -> F: ...
|
|
632
633
|
@overload
|
|
633
634
|
def trace(
|
|
634
|
-
*, name: str | None = ..., defaults:
|
|
635
|
+
*, name: str | None = ..., defaults: Mapping[str, object] | None = ...
|
|
635
636
|
) -> Callable[[F], F]: ...
|
|
636
637
|
|
|
637
638
|
|
|
@@ -639,14 +640,18 @@ def trace(
|
|
|
639
640
|
func: F | None = None,
|
|
640
641
|
*,
|
|
641
642
|
name: str | None = None,
|
|
642
|
-
defaults:
|
|
643
|
+
defaults: Mapping[str, object] | None = None,
|
|
643
644
|
) -> F | Callable[[F], F]:
|
|
644
645
|
"""Traces a function call as a span, usable bare as ``@trace`` or with arguments.
|
|
645
646
|
|
|
646
647
|
Args:
|
|
647
648
|
func: The function being decorated when used bare, otherwise ``None``.
|
|
648
649
|
name: Overrides the span name, which defaults to ``func.__qualname__``.
|
|
649
|
-
defaults: Per-decorator default fields added to every event on the span.
|
|
650
|
+
defaults: Per-decorator default fields added to every event on the span. Any
|
|
651
|
+
``Mapping`` is accepted, and a copy is taken **once here**, at decoration — not per
|
|
652
|
+
call. Before SPEC-051 FR-002 the caller's own object was bound to every span and read
|
|
653
|
+
on the per-event path, so editing it after decoration changed later spans; that is
|
|
654
|
+
tolerable for a ``dict`` and is not for a ``Mapping`` whose ``keys()`` is user code.
|
|
650
655
|
|
|
651
656
|
Returns:
|
|
652
657
|
The wrapped function, or a decorator when called with arguments.
|
|
@@ -654,6 +659,7 @@ def trace(
|
|
|
654
659
|
Raises:
|
|
655
660
|
None.
|
|
656
661
|
"""
|
|
662
|
+
span_defaults = None if defaults is None else dict(defaults)
|
|
657
663
|
|
|
658
664
|
def decorate(fn: F) -> F:
|
|
659
665
|
"""Wraps one function, selecting the sync or async wrapper at decoration time.
|
|
@@ -698,7 +704,7 @@ def trace(
|
|
|
698
704
|
Raises:
|
|
699
705
|
BaseException: Whatever the wrapped coroutine raises, unchanged.
|
|
700
706
|
"""
|
|
701
|
-
span, token, scope = _begin(name or fn.__qualname__,
|
|
707
|
+
span, token, scope = _begin(name or fn.__qualname__, span_defaults)
|
|
702
708
|
status, error = "ok", None
|
|
703
709
|
try:
|
|
704
710
|
result = await fn(*args, **kwargs)
|
|
@@ -734,7 +740,7 @@ def trace(
|
|
|
734
740
|
Raises:
|
|
735
741
|
BaseException: Whatever the wrapped function raises, unchanged.
|
|
736
742
|
"""
|
|
737
|
-
span, token, scope = _begin(name or fn.__qualname__,
|
|
743
|
+
span, token, scope = _begin(name or fn.__qualname__, span_defaults)
|
|
738
744
|
status, error = "ok", None
|
|
739
745
|
try:
|
|
740
746
|
result = fn(*args, **kwargs)
|
|
@@ -20,7 +20,7 @@ class SinkDeliveryError(Exception):
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
@dataclass(frozen=True)
|
|
23
|
+
@dataclass(frozen=True, kw_only=True)
|
|
24
24
|
class SinkLosses:
|
|
25
25
|
"""What a sink discarded or could not confirm, cumulative for its lifetime (FR-002).
|
|
26
26
|
|
|
@@ -30,6 +30,14 @@ class SinkLosses:
|
|
|
30
30
|
lost. ``health().failed_batches`` is the worker-level record of a batch given up on for
|
|
31
31
|
good; this is the sink-level record of everything that did not go through first time.
|
|
32
32
|
|
|
33
|
+
Construction is **keyword-only** (SPEC-051 FR-001), which is a break for a sink written
|
|
34
|
+
against ``0.x``: passing the two counters positionally now raises, and because
|
|
35
|
+
:func:`read_losses`
|
|
36
|
+
swallows a raising accessor by design, that sink's loss reporting degrades to ``None`` —
|
|
37
|
+
"reports nothing" — rather than to an error anyone sees. One keyword each is the fix. The
|
|
38
|
+
same decision empties ``__match_args__``, so a positional ``case SinkLosses(d, f):`` no
|
|
39
|
+
longer matches while ``case SinkLosses(dropped=d, failed=f):`` still does.
|
|
40
|
+
|
|
33
41
|
Attributes:
|
|
34
42
|
dropped: An event the sink discarded before attempting delivery, usually one the
|
|
35
43
|
destination could never have accepted as built, so the fix is upstream in what the
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
from typing import Unpack
|
|
6
7
|
|
|
7
|
-
from log_foundry.sinks.http import HTTPSink, merge_headers
|
|
8
|
+
from log_foundry.sinks.http import HTTPPlatformKwargs, HTTPSink, merge_headers
|
|
8
9
|
|
|
9
10
|
__all__ = ["DatadogSink"]
|
|
10
11
|
|
|
@@ -45,7 +46,7 @@ class DatadogSink(HTTPSink):
|
|
|
45
46
|
site: str = "datadoghq.com",
|
|
46
47
|
service: str | None = None,
|
|
47
48
|
ddtags: str | None = None,
|
|
48
|
-
**http_kwargs:
|
|
49
|
+
**http_kwargs: Unpack[HTTPPlatformKwargs],
|
|
49
50
|
) -> None:
|
|
50
51
|
"""Points the sink at a Datadog site's logs intake.
|
|
51
52
|
|
|
@@ -54,7 +55,9 @@ class DatadogSink(HTTPSink):
|
|
|
54
55
|
site: The Datadog site, which selects the intake host.
|
|
55
56
|
service: Overrides the event's own ``service``, or ``None`` to keep it.
|
|
56
57
|
ddtags: Tags applied to every entry, or ``None`` for none.
|
|
57
|
-
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink
|
|
58
|
+
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink`, typed as
|
|
59
|
+
``HTTPPlatformKwargs`` (SPEC-051 FR-005) — every keyword it takes except
|
|
60
|
+
``body_format``, which this sink pins to Datadog's JSON array.
|
|
58
61
|
|
|
59
62
|
Returns:
|
|
60
63
|
None.
|
|
@@ -67,7 +70,9 @@ class DatadogSink(HTTPSink):
|
|
|
67
70
|
headers = merge_headers({"DD-API-KEY": api_key}, http_kwargs)
|
|
68
71
|
super().__init__(
|
|
69
72
|
f"https://http-intake.logs.{site}/api/v2/logs",
|
|
70
|
-
headers=headers,
|
|
73
|
+
headers=headers,
|
|
74
|
+
body_format="json_array",
|
|
75
|
+
**http_kwargs, # type: ignore[misc]
|
|
71
76
|
)
|
|
72
77
|
|
|
73
78
|
def _render(self, event: dict[str, object]) -> str:
|
{log_foundry-0.10.2.dev108 → log_foundry-0.10.2.dev110}/src/log_foundry/sinks/elasticsearch.py
RENAMED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
from typing import Unpack
|
|
6
7
|
|
|
7
8
|
from log_foundry import _diag
|
|
8
9
|
from log_foundry.sinks._batch import usable_results
|
|
9
10
|
from log_foundry.sinks.base import SinkLosses
|
|
10
|
-
from log_foundry.sinks.http import HTTPSink, _Item
|
|
11
|
+
from log_foundry.sinks.http import HTTPRetryKwargs, HTTPSink, _Item
|
|
11
12
|
|
|
12
13
|
__all__ = ["ElasticsearchSink", "OpenSearchSink"]
|
|
13
14
|
|
|
@@ -48,14 +49,17 @@ class ElasticsearchSink(HTTPSink):
|
|
|
48
49
|
MAX_BATCH_BYTES = 10_000_000
|
|
49
50
|
|
|
50
51
|
def __init__(self, url: str, *, index: str, auth: str | tuple[str, str] | None = None,
|
|
51
|
-
**http_kwargs:
|
|
52
|
+
**http_kwargs: Unpack[HTTPRetryKwargs]) -> None:
|
|
52
53
|
"""Points the sink at a cluster's ``_bulk`` endpoint.
|
|
53
54
|
|
|
54
55
|
Args:
|
|
55
56
|
url: The cluster base URL, to which ``/_bulk`` is appended.
|
|
56
57
|
index: The target index named in every action line.
|
|
57
58
|
auth: A bearer token, or a ``(user, password)`` pair for basic auth.
|
|
58
|
-
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink
|
|
59
|
+
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink`, typed as
|
|
60
|
+
``HTTPRetryKwargs`` (SPEC-051 FR-005) — every keyword it takes except
|
|
61
|
+
``body_format``, pinned to the bulk API's NDJSON, and ``auth``, which is a
|
|
62
|
+
parameter of this sink's own.
|
|
59
63
|
|
|
60
64
|
Returns:
|
|
61
65
|
None.
|
|
@@ -65,7 +69,7 @@ class ElasticsearchSink(HTTPSink):
|
|
|
65
69
|
"""
|
|
66
70
|
self._index = index
|
|
67
71
|
super().__init__(
|
|
68
|
-
url.rstrip("/") + "/_bulk", auth=auth, body_format="ndjson", **http_kwargs
|
|
72
|
+
url.rstrip("/") + "/_bulk", auth=auth, body_format="ndjson", **http_kwargs
|
|
69
73
|
)
|
|
70
74
|
self.item_errors = 0
|
|
71
75
|
self.dropped_unadjudicated = 0
|
|
@@ -186,9 +186,9 @@ class FilteringSink:
|
|
|
186
186
|
|
|
187
187
|
Returns:
|
|
188
188
|
The inner sink's losses. ``None`` passes through unchanged rather than becoming
|
|
189
|
-
``SinkLosses(0, 0)``: FR-003 distinguishes "the sink reports
|
|
190
|
-
reports no loss", and flattening the two would claim a
|
|
191
|
-
that never gave one.
|
|
189
|
+
``SinkLosses(dropped=0, failed=0)``: FR-003 distinguishes "the sink reports
|
|
190
|
+
nothing" from "the sink reports no loss", and flattening the two would claim a
|
|
191
|
+
clean bill of health on a sink that never gave one.
|
|
192
192
|
|
|
193
193
|
Raises:
|
|
194
194
|
None.
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
from typing import Unpack
|
|
6
7
|
|
|
7
|
-
from log_foundry.sinks.http import HTTPSink, merge_headers
|
|
8
|
+
from log_foundry.sinks.http import HTTPPlatformKwargs, HTTPSink, merge_headers
|
|
8
9
|
|
|
9
10
|
__all__ = ["HoneycombSink"]
|
|
10
11
|
|
|
@@ -39,7 +40,7 @@ class HoneycombSink(HTTPSink):
|
|
|
39
40
|
dataset: str,
|
|
40
41
|
*,
|
|
41
42
|
url: str = "https://api.honeycomb.io",
|
|
42
|
-
**http_kwargs:
|
|
43
|
+
**http_kwargs: Unpack[HTTPPlatformKwargs],
|
|
43
44
|
) -> None:
|
|
44
45
|
"""Points the sink at a dataset's batch endpoint.
|
|
45
46
|
|
|
@@ -47,7 +48,9 @@ class HoneycombSink(HTTPSink):
|
|
|
47
48
|
api_key: The key sent as ``X-Honeycomb-Team``.
|
|
48
49
|
dataset: The target dataset, which forms part of the path.
|
|
49
50
|
url: The API base URL.
|
|
50
|
-
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink
|
|
51
|
+
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink`, typed as
|
|
52
|
+
``HTTPPlatformKwargs`` (SPEC-051 FR-005) — every keyword it takes except
|
|
53
|
+
``body_format``, which this sink pins to Honeycomb's JSON array.
|
|
51
54
|
|
|
52
55
|
Returns:
|
|
53
56
|
None.
|
|
@@ -58,7 +61,9 @@ class HoneycombSink(HTTPSink):
|
|
|
58
61
|
headers = merge_headers({"X-Honeycomb-Team": api_key}, http_kwargs)
|
|
59
62
|
super().__init__(
|
|
60
63
|
f"{url.rstrip('/')}/1/batch/{dataset}",
|
|
61
|
-
headers=headers,
|
|
64
|
+
headers=headers,
|
|
65
|
+
body_format="json_array",
|
|
66
|
+
**http_kwargs, # type: ignore[misc]
|
|
62
67
|
)
|
|
63
68
|
|
|
64
69
|
def _render(self, event: dict[str, object]) -> str:
|
|
@@ -8,17 +8,23 @@ import threading
|
|
|
8
8
|
import urllib.error
|
|
9
9
|
import urllib.request
|
|
10
10
|
from base64 import b64encode
|
|
11
|
+
from collections.abc import Callable # noqa: TC003
|
|
11
12
|
from dataclasses import dataclass
|
|
12
|
-
from typing import
|
|
13
|
+
from typing import Any, NoReturn, TypedDict
|
|
13
14
|
|
|
14
15
|
from log_foundry import _diag
|
|
15
16
|
from log_foundry.sinks._retry import clamp_server_delay, wait
|
|
16
17
|
from log_foundry.sinks.base import SinkDeliveryError, SinkLosses
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
__all__ = [
|
|
20
|
+
"HTTPAuthKwargs",
|
|
21
|
+
"HTTPForwardKwargs",
|
|
22
|
+
"HTTPKwargs",
|
|
23
|
+
"HTTPPlatformKwargs",
|
|
24
|
+
"HTTPRetryKwargs",
|
|
25
|
+
"HTTPSink",
|
|
26
|
+
"merge_headers",
|
|
27
|
+
]
|
|
22
28
|
|
|
23
29
|
_BACKOFF_BASE = 0.1
|
|
24
30
|
|
|
@@ -196,7 +202,50 @@ def _no_redirect_opener() -> Callable[..., Any]:
|
|
|
196
202
|
return urllib.request.build_opener(_NoRedirect()).open
|
|
197
203
|
|
|
198
204
|
|
|
199
|
-
|
|
205
|
+
class HTTPForwardKwargs(TypedDict, total=False):
|
|
206
|
+
"""The :class:`HTTPSink` keywords every platform sink in this family can forward.
|
|
207
|
+
|
|
208
|
+
Five shapes, composed by inheritance so each key is declared exactly once, replace the
|
|
209
|
+
``**http_kwargs: object`` that let ``timeout="not-a-float"`` past ``mypy`` and past
|
|
210
|
+
construction (SPEC-051 FR-005). A platform sink cannot forward a keyword it sets itself or
|
|
211
|
+
shadows with a parameter of its own — ``mypy`` refuses a ``**kwargs`` TypedDict key that
|
|
212
|
+
collides with a named parameter — so this is the intersection and the four below widen it.
|
|
213
|
+
Every annotation matches ``HTTPSink.__init__``'s, and a roster test holds them there.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
method: str
|
|
217
|
+
headers: dict[str, str] | None
|
|
218
|
+
gzip: bool
|
|
219
|
+
max_retry_after: float
|
|
220
|
+
max_batch_count: int | None
|
|
221
|
+
max_batch_bytes: int | None
|
|
222
|
+
opener: Callable[..., Any] | None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class HTTPRetryKwargs(HTTPForwardKwargs, total=False):
|
|
226
|
+
""":class:`HTTPForwardKwargs` plus the two a sink with a single transport does not own."""
|
|
227
|
+
|
|
228
|
+
timeout: float
|
|
229
|
+
max_retries: int
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class HTTPAuthKwargs(HTTPForwardKwargs, total=False):
|
|
233
|
+
""":class:`HTTPForwardKwargs` plus ``auth``, for a sink taking no credentials of its own."""
|
|
234
|
+
|
|
235
|
+
auth: str | tuple[str, str] | None
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class HTTPPlatformKwargs(HTTPRetryKwargs, HTTPAuthKwargs, total=False):
|
|
239
|
+
"""Both of those: for a sink that owns only the body format."""
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class HTTPKwargs(HTTPPlatformKwargs, total=False):
|
|
243
|
+
"""Every keyword :class:`HTTPSink` accepts besides its positional ``url``."""
|
|
244
|
+
|
|
245
|
+
body_format: str
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def merge_headers(base: dict[str, str], http_kwargs: HTTPForwardKwargs) -> dict[str, str]:
|
|
200
249
|
"""Merges a platform sink's own headers with any caller-supplied ones, caller winning.
|
|
201
250
|
|
|
202
251
|
This is shared by the SaaS sinks that set a fixed auth header — Datadog, Splunk, New Relic,
|
|
@@ -205,7 +254,11 @@ def merge_headers(base: dict[str, str], http_kwargs: dict[str, object]) -> dict[
|
|
|
205
254
|
Args:
|
|
206
255
|
base: The sink's own headers, updated in place.
|
|
207
256
|
http_kwargs: The caller's keyword arguments, mutated by popping ``headers`` out so the
|
|
208
|
-
rest can be forwarded to :class:`HTTPSink` without a duplicate argument.
|
|
257
|
+
rest can be forwarded to :class:`HTTPSink` without a duplicate argument. Typed as the
|
|
258
|
+
narrowest shape (SPEC-051 FR-005); every wider one is assignable to it, and ``pop`` is
|
|
259
|
+
allowed because each key is ``total=False``. The pop is why a caller may still pass
|
|
260
|
+
``headers``, and — since it happens *here*, in another function — why ``mypy`` reports
|
|
261
|
+
the caller's own forwarding call as a possible duplicate anyway.
|
|
209
262
|
|
|
210
263
|
Returns:
|
|
211
264
|
The merged headers.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TYPE_CHECKING, Unpack
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
import threading
|
|
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
|
|
11
11
|
from log_foundry import _lifecycle
|
|
12
12
|
from log_foundry.sinks._socket import DEFAULT_MAX_DATAGRAM_BYTES, SocketTransport
|
|
13
13
|
from log_foundry.sinks.base import SinkLosses
|
|
14
|
-
from log_foundry.sinks.http import HTTPSink
|
|
14
|
+
from log_foundry.sinks.http import HTTPAuthKwargs, HTTPSink
|
|
15
15
|
|
|
16
16
|
__all__ = ["LogstashSink"]
|
|
17
17
|
|
|
@@ -90,7 +90,7 @@ class LogstashSink:
|
|
|
90
90
|
timeout: float = 5.0,
|
|
91
91
|
max_retries: int = 3,
|
|
92
92
|
max_datagram_bytes: int = DEFAULT_MAX_DATAGRAM_BYTES,
|
|
93
|
-
**http_kwargs:
|
|
93
|
+
**http_kwargs: Unpack[HTTPAuthKwargs],
|
|
94
94
|
) -> None:
|
|
95
95
|
"""Selects and builds exactly one backend.
|
|
96
96
|
|
|
@@ -110,7 +110,11 @@ class LogstashSink:
|
|
|
110
110
|
max_datagram_bytes: In UDP socket mode, the largest datagram to attempt; a frame over
|
|
111
111
|
it is dropped and counted rather than sent, retried and abandoned (SPEC-038 FR-007).
|
|
112
112
|
Ignored in HTTP mode and over TCP, which is a stream.
|
|
113
|
-
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink` in HTTP mode
|
|
113
|
+
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink` in HTTP mode,
|
|
114
|
+
typed as ``HTTPAuthKwargs`` (SPEC-051 FR-005) — every keyword it takes except
|
|
115
|
+
``body_format``, ``timeout`` and ``max_retries``, each a parameter of this sink's
|
|
116
|
+
own: the last two because socket mode uses them, and ``body_format`` because it is
|
|
117
|
+
validated in both modes and defaults differently here.
|
|
114
118
|
|
|
115
119
|
Returns:
|
|
116
120
|
None.
|
|
@@ -127,7 +131,7 @@ class LogstashSink:
|
|
|
127
131
|
if url is not None:
|
|
128
132
|
self._http: HTTPSink | None = HTTPSink(
|
|
129
133
|
url, body_format=body_format, timeout=timeout, max_retries=max_retries,
|
|
130
|
-
**http_kwargs,
|
|
134
|
+
**http_kwargs,
|
|
131
135
|
)
|
|
132
136
|
self._socket: SocketTransport | None = None
|
|
133
137
|
elif host is not None and port is not None:
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
from typing import Unpack
|
|
6
7
|
|
|
7
8
|
from log_foundry.sinks._time import epoch_nanos
|
|
8
|
-
from log_foundry.sinks.http import HTTPSink, _Item
|
|
9
|
+
from log_foundry.sinks.http import HTTPPlatformKwargs, HTTPSink, _Item
|
|
9
10
|
|
|
10
11
|
__all__ = ["LokiSink"]
|
|
11
12
|
|
|
@@ -48,13 +49,15 @@ class LokiSink(HTTPSink):
|
|
|
48
49
|
MAX_BATCH_BYTES = 4_000_000
|
|
49
50
|
|
|
50
51
|
def __init__(self, url: str, *, labels: tuple[str, ...] = ("service", "env", "level"),
|
|
51
|
-
**http_kwargs:
|
|
52
|
+
**http_kwargs: Unpack[HTTPPlatformKwargs]) -> None:
|
|
52
53
|
"""Points the sink at a Loki push endpoint.
|
|
53
54
|
|
|
54
55
|
Args:
|
|
55
56
|
url: The Loki base URL, to which the push path is appended.
|
|
56
57
|
labels: The event keys promoted to stream labels.
|
|
57
|
-
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink
|
|
58
|
+
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink`, typed as
|
|
59
|
+
``HTTPPlatformKwargs`` (SPEC-051 FR-005) — every keyword it takes except
|
|
60
|
+
``body_format``, which this sink pins to Loki's JSON array.
|
|
58
61
|
|
|
59
62
|
Returns:
|
|
60
63
|
None.
|
|
@@ -64,7 +67,7 @@ class LokiSink(HTTPSink):
|
|
|
64
67
|
"""
|
|
65
68
|
self._labels = labels
|
|
66
69
|
super().__init__(
|
|
67
|
-
url.rstrip("/") + _PUSH_PATH, body_format="json_array", **http_kwargs
|
|
70
|
+
url.rstrip("/") + _PUSH_PATH, body_format="json_array", **http_kwargs
|
|
68
71
|
)
|
|
69
72
|
|
|
70
73
|
def _render(self, event: dict[str, object]) -> str:
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from typing import Unpack
|
|
6
|
+
|
|
7
|
+
from log_foundry.sinks.http import HTTPPlatformKwargs, HTTPSink, merge_headers
|
|
6
8
|
|
|
7
9
|
__all__ = ["NewRelicSink"]
|
|
8
10
|
|
|
@@ -29,13 +31,18 @@ class NewRelicSink(HTTPSink):
|
|
|
29
31
|
MAX_BATCH_COUNT = 1000
|
|
30
32
|
MAX_BATCH_BYTES = 1_000_000
|
|
31
33
|
|
|
32
|
-
def __init__(
|
|
34
|
+
def __init__(
|
|
35
|
+
self, api_key: str, *, region: str = "US",
|
|
36
|
+
**http_kwargs: Unpack[HTTPPlatformKwargs],
|
|
37
|
+
) -> None:
|
|
33
38
|
"""Points the sink at a region's Log API endpoint.
|
|
34
39
|
|
|
35
40
|
Args:
|
|
36
41
|
api_key: The key sent as ``Api-Key``.
|
|
37
42
|
region: The account region, matched case-insensitively, which selects the host.
|
|
38
|
-
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink
|
|
43
|
+
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink`, typed as
|
|
44
|
+
``HTTPPlatformKwargs`` (SPEC-051 FR-005) — every keyword it takes except
|
|
45
|
+
``body_format``, which this sink pins to New Relic's JSON array.
|
|
39
46
|
|
|
40
47
|
Returns:
|
|
41
48
|
None.
|
|
@@ -49,5 +56,7 @@ class NewRelicSink(HTTPSink):
|
|
|
49
56
|
headers = merge_headers({"Api-Key": api_key}, http_kwargs)
|
|
50
57
|
super().__init__(
|
|
51
58
|
f"https://{_HOSTS[region]}/log/v1",
|
|
52
|
-
headers=headers,
|
|
59
|
+
headers=headers,
|
|
60
|
+
body_format="json_array",
|
|
61
|
+
**http_kwargs, # type: ignore[misc]
|
|
53
62
|
)
|
|
@@ -12,10 +12,14 @@ from log_foundry import _diag, _lifecycle
|
|
|
12
12
|
from log_foundry.sinks.base import SinkDeliveryError, SinkLosses
|
|
13
13
|
from log_foundry.sinks.http import HTTPSink
|
|
14
14
|
|
|
15
|
-
__all__ = ["SentrySink"]
|
|
15
|
+
__all__ = ["Backend", "SentrySink"]
|
|
16
16
|
|
|
17
17
|
Backend = Literal["auto", "sdk", "http"]
|
|
18
|
-
"""Which transport a :class:`SentrySink` uses.
|
|
18
|
+
"""Which transport a :class:`SentrySink` uses.
|
|
19
|
+
|
|
20
|
+
Exported (SPEC-051 FR-004) because it is in a public signature: passing the literal is still
|
|
21
|
+
the ordinary way to call, but a caller annotating a variable that holds one needs the name.
|
|
22
|
+
"""
|
|
19
23
|
|
|
20
24
|
_BACKENDS: Final = get_args(Backend)
|
|
21
25
|
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
from typing import Unpack
|
|
6
7
|
|
|
7
8
|
from log_foundry.sinks._time import epoch_seconds
|
|
8
|
-
from log_foundry.sinks.http import HTTPSink, _Item, merge_headers
|
|
9
|
+
from log_foundry.sinks.http import HTTPKwargs, HTTPSink, _Item, merge_headers
|
|
9
10
|
|
|
10
11
|
__all__ = ["SplunkHECSink"]
|
|
11
12
|
|
|
@@ -44,7 +45,7 @@ class SplunkHECSink(HTTPSink):
|
|
|
44
45
|
*,
|
|
45
46
|
host: str | None = None,
|
|
46
47
|
source: str = "log-foundry",
|
|
47
|
-
**http_kwargs:
|
|
48
|
+
**http_kwargs: Unpack[HTTPKwargs],
|
|
48
49
|
) -> None:
|
|
49
50
|
"""Points the sink at a collector endpoint.
|
|
50
51
|
|
|
@@ -53,7 +54,9 @@ class SplunkHECSink(HTTPSink):
|
|
|
53
54
|
token: The HEC token sent in the ``Authorization`` header.
|
|
54
55
|
host: The ``host`` stamped on each envelope, or ``None`` to omit it.
|
|
55
56
|
source: The ``source`` stamped on each envelope.
|
|
56
|
-
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink
|
|
57
|
+
**http_kwargs: Forwarded to :class:`~log_foundry.sinks.http.HTTPSink`, typed as
|
|
58
|
+
``HTTPKwargs`` (SPEC-051 FR-005) — every keyword it takes, since this sink pins
|
|
59
|
+
none of them.
|
|
57
60
|
|
|
58
61
|
Returns:
|
|
59
62
|
None.
|
|
@@ -64,7 +67,7 @@ class SplunkHECSink(HTTPSink):
|
|
|
64
67
|
self._host = host
|
|
65
68
|
self._source = source
|
|
66
69
|
headers = merge_headers({"Authorization": f"Splunk {token}"}, http_kwargs)
|
|
67
|
-
super().__init__(url, headers=headers, **http_kwargs) # type: ignore[
|
|
70
|
+
super().__init__(url, headers=headers, **http_kwargs) # type: ignore[misc]
|
|
68
71
|
|
|
69
72
|
def _render(self, event: dict[str, object]) -> str:
|
|
70
73
|
"""Serializes one event's HEC envelope (FR-008).
|
|
@@ -16,7 +16,7 @@ from log_foundry import _diag
|
|
|
16
16
|
from log_foundry.sinks._retry import wait
|
|
17
17
|
from log_foundry.sinks.base import SinkDeliveryError, SinkLosses
|
|
18
18
|
|
|
19
|
-
__all__ = ["SQSSink"]
|
|
19
|
+
__all__ = ["DedupIdSource", "GroupIdSource", "SQSSink"]
|
|
20
20
|
|
|
21
21
|
_BACKOFF_BASE = 0.1
|
|
22
22
|
|
|
@@ -27,7 +27,10 @@ MAX_ID_LEN = 128
|
|
|
27
27
|
"""SQS maximum length for ``MessageGroupId`` and ``MessageDeduplicationId``."""
|
|
28
28
|
|
|
29
29
|
GroupIdSource = str | Callable[[dict[str, object]], str] | None
|
|
30
|
+
"""What ``message_group_id=`` accepts: an event key to read, a callable, or ``None``."""
|
|
31
|
+
|
|
30
32
|
DedupIdSource = Callable[[dict[str, object]], str] | None
|
|
33
|
+
"""What ``message_deduplication_id=`` accepts: a callable, or ``None`` for a minted UUID."""
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
class _Prepared(NamedTuple):
|
|
@@ -154,9 +154,9 @@ class TransformSink:
|
|
|
154
154
|
|
|
155
155
|
Returns:
|
|
156
156
|
The inner sink's losses. ``None`` passes through unchanged rather than becoming
|
|
157
|
-
``SinkLosses(0, 0)``: FR-003 distinguishes "the sink reports
|
|
158
|
-
reports no loss", and flattening the two would claim a
|
|
159
|
-
that never gave one.
|
|
157
|
+
``SinkLosses(dropped=0, failed=0)``: FR-003 distinguishes "the sink reports
|
|
158
|
+
nothing" from "the sink reports no loss", and flattening the two would claim a
|
|
159
|
+
clean bill of health on a sink that never gave one.
|
|
160
160
|
|
|
161
161
|
Raises:
|
|
162
162
|
None.
|
|
@@ -83,14 +83,17 @@ def _bounded_seconds(timeout: float | None) -> str:
|
|
|
83
83
|
return "?"
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
@dataclass(frozen=True)
|
|
86
|
+
@dataclass(frozen=True, kw_only=True)
|
|
87
87
|
class Health:
|
|
88
88
|
"""A point-in-time snapshot of the worker's delivery counters (SPEC-017 FR-005).
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
order, so
|
|
92
|
-
construction
|
|
93
|
-
|
|
90
|
+
Construction is **keyword-only** (SPEC-051 FR-001), which is what makes appending a field
|
|
91
|
+
safe: order is not part of the contract, so a new counter can go anywhere and no third-party
|
|
92
|
+
construction can bind to a position. It was previously ordered — every field after
|
|
93
|
+
``failed_batches`` is defaulted and was appended in the order the specs landed. The claim
|
|
94
|
+
that a caller could also subscript this, carried here from the ``NamedTuple`` SPEC-034
|
|
95
|
+
replaced, was false from the moment it became a dataclass: ``len(health())`` raises
|
|
96
|
+
``TypeError``, and ``README.md`` said the opposite correctly throughout.
|
|
94
97
|
|
|
95
98
|
Attributes:
|
|
96
99
|
queued: Submissions currently buffered. Approximate by nature: it is read without
|
|
@@ -148,8 +151,10 @@ class Health:
|
|
|
148
151
|
closing_sinks: Swapped-out sinks whose ``close()`` is running *at this instant* — a live
|
|
149
152
|
gauge rather than a counter, so it falls as well as rises. ~~the only field here that
|
|
150
153
|
can fall~~ — struck (SPEC-034 AC-2c): ``queued`` falls on every drain. Those two are the
|
|
151
|
-
gauges and
|
|
152
|
-
alerting on "any non-zero" needs and which no name
|
|
154
|
+
only gauges and every other integer here is monotonic, which is the distinction an
|
|
155
|
+
operator alerting on "any non-zero" needs and which no name encodes. Stated as the rule
|
|
156
|
+
rather than as a count: it said "the other five" until SPEC-051, having gone stale when
|
|
157
|
+
SPEC-036 appended ``orphan_lost`` and ``in_span_lost`` and made it six. A close is
|
|
153
158
|
bounded only in how long ``configure()`` waits for it, so this is how a destination
|
|
154
159
|
stuck in ``close()`` becomes visible at all. Reading it non-zero once means a swap just
|
|
155
160
|
happened; reading it non-zero repeatedly means a close is not coming back, and that sink
|
|
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.dev108 → log_foundry-0.10.2.dev110}/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
|