log-foundry 0.10.2.dev48__tar.gz → 0.10.2.dev49__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.dev48 → log_foundry-0.10.2.dev49}/PKG-INFO +32 -3
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/README.md +31 -2
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/datadog.py +18 -9
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/elasticsearch.py +44 -26
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/honeycomb.py +15 -9
- log_foundry-0.10.2.dev49/src/log_foundry/sinks/http.py +949 -0
- log_foundry-0.10.2.dev49/src/log_foundry/sinks/loki.py +167 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/newrelic.py +9 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/splunk.py +35 -10
- log_foundry-0.10.2.dev48/src/log_foundry/sinks/http.py +0 -417
- log_foundry-0.10.2.dev48/src/log_foundry/sinks/loki.py +0 -73
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/LICENSE +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/_diag.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/_lifecycle.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/decorator.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/results.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/_retry.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/util.py +0 -0
- {log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: log-foundry
|
|
3
|
-
Version: 0.10.2.
|
|
3
|
+
Version: 0.10.2.dev49
|
|
4
4
|
Summary: Generate logs for your console and JSON events for downstream consumption.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -609,11 +609,40 @@ lf.configure(sink=RotatingFileSink("app.log.jsonl", max_bytes=10_000_000, backup
|
|
|
609
609
|
|
|
610
610
|
All build on `HTTPSink` (stdlib `urllib`): they POST batches with bounded `429`/`5xx` retry
|
|
611
611
|
(honoring `Retry-After`) and need **no** extra. On the specialized sinks, `**http_kwargs` forwards
|
|
612
|
-
to `HTTPSink` (`headers=`, `auth=`, `gzip=`, `timeout=`, `max_retries
|
|
612
|
+
to `HTTPSink` (`headers=`, `auth=`, `gzip=`, `timeout=`, `max_retries=`, `max_retry_after=`,
|
|
613
|
+
`max_batch_count=`, `max_batch_bytes=`, `opener=`).
|
|
614
|
+
|
|
615
|
+
Each sink **re-chunks** a batch to its destination's limits, so one large `emit` — the whole
|
|
616
|
+
pending backlog at process exit, for instance — becomes several requests the destination will
|
|
617
|
+
accept rather than one it rejects whole. Every subclass sets its own defaults from its vendor's
|
|
618
|
+
documentation where there is one; override with `max_batch_count=` / `max_batch_bytes=`. An event
|
|
619
|
+
too large to be sent on its own is dropped and counted in `health().sink.dropped`.
|
|
620
|
+
|
|
621
|
+
| Sink | `max_batch_count` | `max_batch_bytes` | Where the figure comes from |
|
|
622
|
+
|---|---|---|---|
|
|
623
|
+
| `DatadogSink` | 1,000 | 5,000,000 | both documented by Datadog for the logs intake |
|
|
624
|
+
| `NewRelicSink` | 1,000 | 1,000,000 | the Log API's documented 1 MB (10⁶ bytes) per POST |
|
|
625
|
+
| `HoneycombSink` | 1,000 | 1,000,000 | Honeycomb's documented 1 MB of uncompressed JSON |
|
|
626
|
+
| `ElasticsearchSink` / `OpenSearchSink` | 1,000 | 10,000,000 | chosen below the documented 100 MB `http.max_content_length` |
|
|
627
|
+
| `LokiSink` | 1,000 | 4,000,000 | chosen; Loki's own cap is an operator-tunable server setting |
|
|
628
|
+
| `SplunkHECSink` | 1,000 | 1,000,000 | chosen; Splunk publishes no fixed HEC payload limit |
|
|
629
|
+
| `LogstashSink` (HTTP mode) | 1,000 | 5,000,000 | inherits the generic defaults |
|
|
630
|
+
| `HTTPSink` (generic) | 1,000 | 5,000,000 | chosen, for an endpoint the library knows nothing about |
|
|
631
|
+
|
|
632
|
+
A count of 1,000 is Datadog's published array limit and a conservative default elsewhere — no
|
|
633
|
+
destination in this family documents a smaller one. `DatadogSink` additionally enforces a
|
|
634
|
+
1,000,000-byte limit on a *single* log, which is the one case where a destination's per-event cap
|
|
635
|
+
is stricter than its per-request one.
|
|
636
|
+
|
|
637
|
+
Two consequences worth knowing. An event too large to be sent on its own is **dropped**, not
|
|
638
|
+
attempted — including in `LogstashSink`'s HTTP mode, which previously put any size on the wire.
|
|
639
|
+
And because a request now carries one chunk rather than the whole batch, a partial failure is
|
|
640
|
+
reported through `health().sink` rather than raised: `flush()` returning `True` means the drain
|
|
641
|
+
completed, not that every chunk of it landed.
|
|
613
642
|
|
|
614
643
|
| Sink | Import from | Configure |
|
|
615
644
|
|---|---|---|
|
|
616
|
-
| `HTTPSink` | `log_foundry.sinks.http` | `HTTPSink(url, *, method="POST", headers=None, auth=None, body_format="ndjson", timeout=5.0, gzip=False, max_retries=3)` — generic POST. `auth` is a bearer-token `str` or `(user, pass)` for basic; `body_format` is `"ndjson"` or `"json_array"` |
|
|
645
|
+
| `HTTPSink` | `log_foundry.sinks.http` | `HTTPSink(url, *, method="POST", headers=None, auth=None, body_format="ndjson", timeout=5.0, gzip=False, max_retries=3, max_retry_after=30.0, max_batch_count=None, max_batch_bytes=None, opener=None)` — generic POST. `auth` is a bearer-token `str` or `(user, pass)` for basic; `body_format` is `"ndjson"` or `"json_array"`; the two `max_batch_*` default to `None`, meaning "use this class's limits" from the table below (1,000 / 5,000,000 here); `opener` injects a `urlopen`-shaped callable for tests |
|
|
617
646
|
| `ElasticsearchSink` | `log_foundry.sinks.elasticsearch` | `ElasticsearchSink(url, *, index, auth=None, **http_kwargs)` — POST to `_bulk`, parsing per-item errors (`.item_errors`) |
|
|
618
647
|
| `OpenSearchSink` | `log_foundry.sinks.elasticsearch` | same signature as `ElasticsearchSink` (identical bulk protocol) |
|
|
619
648
|
| `LokiSink` | `log_foundry.sinks.loki` | `LokiSink(url, *, labels=("service", "env", "level"), **http_kwargs)` — Grafana Loki push API |
|
|
@@ -573,11 +573,40 @@ lf.configure(sink=RotatingFileSink("app.log.jsonl", max_bytes=10_000_000, backup
|
|
|
573
573
|
|
|
574
574
|
All build on `HTTPSink` (stdlib `urllib`): they POST batches with bounded `429`/`5xx` retry
|
|
575
575
|
(honoring `Retry-After`) and need **no** extra. On the specialized sinks, `**http_kwargs` forwards
|
|
576
|
-
to `HTTPSink` (`headers=`, `auth=`, `gzip=`, `timeout=`, `max_retries
|
|
576
|
+
to `HTTPSink` (`headers=`, `auth=`, `gzip=`, `timeout=`, `max_retries=`, `max_retry_after=`,
|
|
577
|
+
`max_batch_count=`, `max_batch_bytes=`, `opener=`).
|
|
578
|
+
|
|
579
|
+
Each sink **re-chunks** a batch to its destination's limits, so one large `emit` — the whole
|
|
580
|
+
pending backlog at process exit, for instance — becomes several requests the destination will
|
|
581
|
+
accept rather than one it rejects whole. Every subclass sets its own defaults from its vendor's
|
|
582
|
+
documentation where there is one; override with `max_batch_count=` / `max_batch_bytes=`. An event
|
|
583
|
+
too large to be sent on its own is dropped and counted in `health().sink.dropped`.
|
|
584
|
+
|
|
585
|
+
| Sink | `max_batch_count` | `max_batch_bytes` | Where the figure comes from |
|
|
586
|
+
|---|---|---|---|
|
|
587
|
+
| `DatadogSink` | 1,000 | 5,000,000 | both documented by Datadog for the logs intake |
|
|
588
|
+
| `NewRelicSink` | 1,000 | 1,000,000 | the Log API's documented 1 MB (10⁶ bytes) per POST |
|
|
589
|
+
| `HoneycombSink` | 1,000 | 1,000,000 | Honeycomb's documented 1 MB of uncompressed JSON |
|
|
590
|
+
| `ElasticsearchSink` / `OpenSearchSink` | 1,000 | 10,000,000 | chosen below the documented 100 MB `http.max_content_length` |
|
|
591
|
+
| `LokiSink` | 1,000 | 4,000,000 | chosen; Loki's own cap is an operator-tunable server setting |
|
|
592
|
+
| `SplunkHECSink` | 1,000 | 1,000,000 | chosen; Splunk publishes no fixed HEC payload limit |
|
|
593
|
+
| `LogstashSink` (HTTP mode) | 1,000 | 5,000,000 | inherits the generic defaults |
|
|
594
|
+
| `HTTPSink` (generic) | 1,000 | 5,000,000 | chosen, for an endpoint the library knows nothing about |
|
|
595
|
+
|
|
596
|
+
A count of 1,000 is Datadog's published array limit and a conservative default elsewhere — no
|
|
597
|
+
destination in this family documents a smaller one. `DatadogSink` additionally enforces a
|
|
598
|
+
1,000,000-byte limit on a *single* log, which is the one case where a destination's per-event cap
|
|
599
|
+
is stricter than its per-request one.
|
|
600
|
+
|
|
601
|
+
Two consequences worth knowing. An event too large to be sent on its own is **dropped**, not
|
|
602
|
+
attempted — including in `LogstashSink`'s HTTP mode, which previously put any size on the wire.
|
|
603
|
+
And because a request now carries one chunk rather than the whole batch, a partial failure is
|
|
604
|
+
reported through `health().sink` rather than raised: `flush()` returning `True` means the drain
|
|
605
|
+
completed, not that every chunk of it landed.
|
|
577
606
|
|
|
578
607
|
| Sink | Import from | Configure |
|
|
579
608
|
|---|---|---|
|
|
580
|
-
| `HTTPSink` | `log_foundry.sinks.http` | `HTTPSink(url, *, method="POST", headers=None, auth=None, body_format="ndjson", timeout=5.0, gzip=False, max_retries=3)` — generic POST. `auth` is a bearer-token `str` or `(user, pass)` for basic; `body_format` is `"ndjson"` or `"json_array"` |
|
|
609
|
+
| `HTTPSink` | `log_foundry.sinks.http` | `HTTPSink(url, *, method="POST", headers=None, auth=None, body_format="ndjson", timeout=5.0, gzip=False, max_retries=3, max_retry_after=30.0, max_batch_count=None, max_batch_bytes=None, opener=None)` — generic POST. `auth` is a bearer-token `str` or `(user, pass)` for basic; `body_format` is `"ndjson"` or `"json_array"`; the two `max_batch_*` default to `None`, meaning "use this class's limits" from the table below (1,000 / 5,000,000 here); `opener` injects a `urlopen`-shaped callable for tests |
|
|
581
610
|
| `ElasticsearchSink` | `log_foundry.sinks.elasticsearch` | `ElasticsearchSink(url, *, index, auth=None, **http_kwargs)` — POST to `_bulk`, parsing per-item errors (`.item_errors`) |
|
|
582
611
|
| `OpenSearchSink` | `log_foundry.sinks.elasticsearch` | same signature as `ElasticsearchSink` (identical bulk protocol) |
|
|
583
612
|
| `LokiSink` | `log_foundry.sinks.loki` | `LokiSink(url, *, labels=("service", "env", "level"), **http_kwargs)` — Grafana Loki push API |
|
|
@@ -20,8 +20,20 @@ class DatadogSink(HTTPSink):
|
|
|
20
20
|
It takes **no** transport lock (SPEC-028 FR-002) and **adds no post-close guard**
|
|
21
21
|
(SPEC-032 FR-003), for the reasons :class:`~log_foundry.sinks.http.HTTPSink` records: there
|
|
22
22
|
is no transport held and ``close()`` releases nothing.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
MAX_BATCH_COUNT: 1,000 — Datadog's documented maximum array size for the logs intake.
|
|
26
|
+
MAX_BATCH_BYTES: 5,000,000 — its documented maximum uncompressed payload.
|
|
27
|
+
MAX_EVENT_BYTES: 1,000,000 — its documented maximum for a *single* log. This is the one
|
|
28
|
+
sink in the family whose per-event limit is stricter than its request limit, so without
|
|
29
|
+
it a 2 MB event passes the 5 MB request budget and is rejected by a limit the budget
|
|
30
|
+
cannot see. All three are the vendor's own figures, from the Logs API's send-logs limits.
|
|
23
31
|
"""
|
|
24
32
|
|
|
33
|
+
MAX_BATCH_COUNT = 1000
|
|
34
|
+
MAX_EVENT_BYTES = 1_000_000
|
|
35
|
+
MAX_BATCH_BYTES = 5_000_000
|
|
36
|
+
|
|
25
37
|
def __init__(
|
|
26
38
|
self,
|
|
27
39
|
api_key: str,
|
|
@@ -54,22 +66,19 @@ class DatadogSink(HTTPSink):
|
|
|
54
66
|
headers=headers, body_format="json_array", **http_kwargs, # type: ignore[arg-type]
|
|
55
67
|
)
|
|
56
68
|
|
|
57
|
-
def
|
|
58
|
-
"""
|
|
69
|
+
def _render(self, event: dict[str, object]) -> str:
|
|
70
|
+
"""Serializes one enriched entry for the JSON array (FR-007).
|
|
59
71
|
|
|
60
72
|
Args:
|
|
61
|
-
|
|
73
|
+
event: The event to enrich and serialize.
|
|
62
74
|
|
|
63
75
|
Returns:
|
|
64
|
-
|
|
76
|
+
The serialized entry.
|
|
65
77
|
|
|
66
78
|
Raises:
|
|
67
|
-
|
|
79
|
+
TypeError: If the event is not JSON-serializable, which ``sanitize`` prevents.
|
|
68
80
|
"""
|
|
69
|
-
|
|
70
|
-
return
|
|
71
|
-
body = json.dumps([self._entry(event) for event in batch]).encode("utf-8")
|
|
72
|
-
self._send(body, content_type="application/json")
|
|
81
|
+
return json.dumps(self._entry(event))
|
|
73
82
|
|
|
74
83
|
def _entry(self, event: dict[str, object]) -> dict[str, object]:
|
|
75
84
|
"""Copies an event and applies the configured Datadog enrichment.
|
{log_foundry-0.10.2.dev48 → log_foundry-0.10.2.dev49}/src/log_foundry/sinks/elasticsearch.py
RENAMED
|
@@ -6,8 +6,8 @@ import json
|
|
|
6
6
|
|
|
7
7
|
from log_foundry import _diag
|
|
8
8
|
from log_foundry.sinks._batch import usable_results
|
|
9
|
-
from log_foundry.sinks.base import
|
|
10
|
-
from log_foundry.sinks.http import HTTPSink
|
|
9
|
+
from log_foundry.sinks.base import SinkLosses
|
|
10
|
+
from log_foundry.sinks.http import HTTPSink, _Item
|
|
11
11
|
|
|
12
12
|
__all__ = ["ElasticsearchSink", "OpenSearchSink"]
|
|
13
13
|
|
|
@@ -29,8 +29,20 @@ class ElasticsearchSink(HTTPSink):
|
|
|
29
29
|
It takes **no** transport lock (SPEC-028 FR-002) and **adds no post-close guard**
|
|
30
30
|
(SPEC-032 FR-003), for the reasons :class:`~log_foundry.sinks.http.HTTPSink` records: there
|
|
31
31
|
is no transport held and ``close()`` releases nothing.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
MAX_BATCH_COUNT: 1,000 — this library's conservative default; Elasticsearch bounds a bulk
|
|
35
|
+
request by size, not by document count.
|
|
36
|
+
MAX_BATCH_BYTES: 10,000,000 — chosen *below* the documented hard limit rather than at it.
|
|
37
|
+
Elasticsearch "limits the maximum size of a HTTP request to 100mb by default", but its
|
|
38
|
+
bulk guidance is to find a working size by experiment rather than to send the largest
|
|
39
|
+
request the server will accept, and a 100 MB bulk is a poor default for a log shipper.
|
|
40
|
+
Raise it with ``max_batch_bytes=``.
|
|
32
41
|
"""
|
|
33
42
|
|
|
43
|
+
MAX_BATCH_COUNT = 1000
|
|
44
|
+
MAX_BATCH_BYTES = 10_000_000
|
|
45
|
+
|
|
34
46
|
def __init__(self, url: str, *, index: str, auth: str | tuple[str, str] | None = None,
|
|
35
47
|
**http_kwargs: object) -> None:
|
|
36
48
|
"""Points the sink at a cluster's ``_bulk`` endpoint.
|
|
@@ -54,38 +66,44 @@ class ElasticsearchSink(HTTPSink):
|
|
|
54
66
|
self.item_errors = 0
|
|
55
67
|
self.dropped_unadjudicated = 0
|
|
56
68
|
|
|
57
|
-
def
|
|
58
|
-
"""
|
|
69
|
+
def _render(self, event: dict[str, object]) -> str:
|
|
70
|
+
"""Serializes one event as its ``_bulk`` action line and source line.
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
Both lines are one item so the pair can never be split across two requests, and so the
|
|
73
|
+
chunker's byte budget charges the action line the wire actually carries.
|
|
62
74
|
|
|
63
75
|
Args:
|
|
64
|
-
|
|
76
|
+
event: The event to index.
|
|
65
77
|
|
|
66
78
|
Returns:
|
|
67
|
-
|
|
79
|
+
The two newline-joined lines, without the trailing newline :meth:`_body` adds.
|
|
68
80
|
|
|
69
81
|
Raises:
|
|
70
|
-
|
|
71
|
-
the ``200`` indexed nothing — a total failure like any other, which must reach the
|
|
72
|
-
worker (FR-001). A retry cannot duplicate, and where the cause is permanent the
|
|
73
|
-
worker abandons the batch after its bound and records it, which beats a silent
|
|
74
|
-
success. A response rejecting some items stays partial and is reported through
|
|
75
|
-
:meth:`losses`.
|
|
82
|
+
TypeError: If the event is not JSON-serializable, which ``sanitize`` prevents.
|
|
76
83
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
action = json.dumps({"index": {"_index": self._index}})
|
|
85
|
+
return f"{action}\n{json.dumps(event)}"
|
|
86
|
+
|
|
87
|
+
def _handle_response(self, payload: bytes, items: list[_Item]) -> bool:
|
|
88
|
+
"""Parses the chunk's ``_bulk`` items and reports whether anything was indexed.
|
|
89
|
+
|
|
90
|
+
Returning ``False`` rather than raising is what lets a sibling chunk that succeeded
|
|
91
|
+
stand: :meth:`~log_foundry.sinks.http.HTTPSink.emit` raises only when *every* chunk came
|
|
92
|
+
back empty-handed, which is the total failure the worker's retry exists for, while a
|
|
93
|
+
batch that indexed some of its chunks must not be re-sent whole (SPEC-026 FR-001).
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
payload: The response body.
|
|
97
|
+
items: The chunk the response describes.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
False when every item in the chunk carried an error, True otherwise — including for a
|
|
101
|
+
body this sink cannot read, since the request itself succeeded.
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
None.
|
|
105
|
+
"""
|
|
106
|
+
return not self._parse_bulk_response(payload, len(items))
|
|
89
107
|
|
|
90
108
|
def losses(self) -> SinkLosses:
|
|
91
109
|
"""Reports abandoned requests plus server-rejected bulk items (SPEC-026 FR-002).
|
|
@@ -18,8 +18,17 @@ class HoneycombSink(HTTPSink):
|
|
|
18
18
|
It takes **no** transport lock (SPEC-028 FR-002) and **adds no post-close guard**
|
|
19
19
|
(SPEC-032 FR-003), for the reasons :class:`~log_foundry.sinks.http.HTTPSink` records: there
|
|
20
20
|
is no transport held and ``close()`` releases nothing.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
MAX_BATCH_COUNT: 1,000 — this library's conservative default, since Honeycomb documents no
|
|
24
|
+
maximum event count for the batch endpoint.
|
|
25
|
+
MAX_BATCH_BYTES: 1,000,000 — Honeycomb's documented 1 MB of uncompressed JSON for the
|
|
26
|
+
Create Events endpoint.
|
|
21
27
|
"""
|
|
22
28
|
|
|
29
|
+
MAX_BATCH_COUNT = 1000
|
|
30
|
+
MAX_BATCH_BYTES = 1_000_000
|
|
31
|
+
|
|
23
32
|
def __init__(
|
|
24
33
|
self,
|
|
25
34
|
api_key: str,
|
|
@@ -48,19 +57,16 @@ class HoneycombSink(HTTPSink):
|
|
|
48
57
|
headers=headers, body_format="json_array", **http_kwargs, # type: ignore[arg-type]
|
|
49
58
|
)
|
|
50
59
|
|
|
51
|
-
def
|
|
52
|
-
"""
|
|
60
|
+
def _render(self, event: dict[str, object]) -> str:
|
|
61
|
+
"""Serializes one event in Honeycomb's ``{"data": event}`` batch shape (FR-010).
|
|
53
62
|
|
|
54
63
|
Args:
|
|
55
|
-
|
|
64
|
+
event: The event to wrap and serialize.
|
|
56
65
|
|
|
57
66
|
Returns:
|
|
58
|
-
|
|
67
|
+
The serialized entry.
|
|
59
68
|
|
|
60
69
|
Raises:
|
|
61
|
-
|
|
70
|
+
TypeError: If the event is not JSON-serializable, which ``sanitize`` prevents.
|
|
62
71
|
"""
|
|
63
|
-
|
|
64
|
-
return
|
|
65
|
-
body = json.dumps([{"data": event} for event in batch]).encode("utf-8")
|
|
66
|
-
self._send(body, content_type="application/json")
|
|
72
|
+
return json.dumps({"data": event})
|