log-foundry 0.10.2.dev45__tar.gz → 0.10.2.dev47__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.dev45 → log_foundry-0.10.2.dev47}/PKG-INFO +10 -2
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/README.md +9 -1
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/api.py +20 -8
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sanitize.py +58 -2
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/LICENSE +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/_diag.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/_lifecycle.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/decorator.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/results.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/_retry.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/src/log_foundry/sinks/util.py +0 -0
- {log_foundry-0.10.2.dev45 → log_foundry-0.10.2.dev47}/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.dev47
|
|
4
4
|
Summary: Generate logs for your console and JSON events for downstream consumption.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -1127,7 +1127,15 @@ entries and `max_depth` levels. `datetime`, `UUID`, `Decimal`, `bytes` and frien
|
|
|
1127
1127
|
strings; anything with no JSON form becomes `<unserializable: TypeName>` — the type name only,
|
|
1128
1128
|
never a `repr`, so coercion can never leak a value the library was careful not to capture.
|
|
1129
1129
|
|
|
1130
|
-
|
|
1130
|
+
**Floats that are not finite are replaced.** `NaN`, `Infinity` and `-Infinity` are values Python
|
|
1131
|
+
produces readily — a division that underflowed, a ratio over an empty window — and `json.dumps`
|
|
1132
|
+
writes all three happily. RFC 8259 defines none of them, so a strict consumer (Fluent Bit, a
|
|
1133
|
+
Logstash `json` codec, Jackson behind Elasticsearch) rejects the **whole record**, with nothing on
|
|
1134
|
+
this side to see. Each becomes `<float: nan>`, `<float: inf>` or `<float: -inf>` — which one it
|
|
1135
|
+
was is kept, because that is the only information the field still carried. Ordinary floats,
|
|
1136
|
+
including `-0.0` and subnormals, pass through untouched.
|
|
1137
|
+
|
|
1138
|
+
Integers are the other case worth knowing about. They are passed through unchanged — an ID or an
|
|
1131
1139
|
amount stays a number, at full precision — but an integer too long to *render* is replaced by
|
|
1132
1140
|
`<int: ~N digits>`. CPython refuses to convert an integer past `sys.get_int_max_str_digits()`
|
|
1133
1141
|
decimal digits (**4300** by default) and raises, and `json.dumps` inherits that refusal, so with
|
|
@@ -1091,7 +1091,15 @@ entries and `max_depth` levels. `datetime`, `UUID`, `Decimal`, `bytes` and frien
|
|
|
1091
1091
|
strings; anything with no JSON form becomes `<unserializable: TypeName>` — the type name only,
|
|
1092
1092
|
never a `repr`, so coercion can never leak a value the library was careful not to capture.
|
|
1093
1093
|
|
|
1094
|
-
|
|
1094
|
+
**Floats that are not finite are replaced.** `NaN`, `Infinity` and `-Infinity` are values Python
|
|
1095
|
+
produces readily — a division that underflowed, a ratio over an empty window — and `json.dumps`
|
|
1096
|
+
writes all three happily. RFC 8259 defines none of them, so a strict consumer (Fluent Bit, a
|
|
1097
|
+
Logstash `json` codec, Jackson behind Elasticsearch) rejects the **whole record**, with nothing on
|
|
1098
|
+
this side to see. Each becomes `<float: nan>`, `<float: inf>` or `<float: -inf>` — which one it
|
|
1099
|
+
was is kept, because that is the only information the field still carried. Ordinary floats,
|
|
1100
|
+
including `-0.0` and subnormals, pass through untouched.
|
|
1101
|
+
|
|
1102
|
+
Integers are the other case worth knowing about. They are passed through unchanged — an ID or an
|
|
1095
1103
|
amount stays a number, at full precision — but an integer too long to *render* is replaced by
|
|
1096
1104
|
`<int: ~N digits>`. CPython refuses to convert an integer past `sys.get_int_max_str_digits()`
|
|
1097
1105
|
decimal digits (**4300** by default) and raises, and `json.dumps` inherits that refusal, so with
|
|
@@ -57,12 +57,21 @@ def _log(level: str, message: str, echo: bool, fields: dict[str, object]) -> Non
|
|
|
57
57
|
sink that was written to. ``_ensure_sink`` is resolved first, so a sink that fails to
|
|
58
58
|
*construct* arms nothing: there is nothing to close.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
worker between them to absorb a failure
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
**Both branches are guarded.** The orphan branch reaches the sink on the caller's own
|
|
61
|
+
thread, with no worker between them to absorb a failure (SPEC-025 FR-003) — ``_ensure_sink``
|
|
62
|
+
constructs the sink on first use, so a sink that fails to build raises here too.
|
|
63
|
+
|
|
64
|
+
~~The in-span branch is deliberately left untouched, since it only appends to a list.~~ —
|
|
65
|
+
struck (SPEC-021), and it is why that branch shipped unguarded: it does not *only* append.
|
|
66
|
+
It calls ``build_event``, which calls ``truncate_str``, which calls ``value.encode`` — so
|
|
67
|
+
``info(some_exception)``, a slip ``mypy`` catches only at typed call sites, returned normally
|
|
68
|
+
on the orphan path and killed the caller's function inside a span. Worse than a crash: the
|
|
69
|
+
decorator's own handler then recorded the span ``status=error`` with an ``error.type`` of
|
|
70
|
+
``AttributeError`` that the caller's code never raised — wrong data *and* a broken caller.
|
|
71
|
+
A guard that reads "this cannot fail" is a guard that stops being true when the code under
|
|
72
|
+
it changes, and this one had already stopped.
|
|
73
|
+
|
|
74
|
+
The echo runs after the emit, so a closed or redirected stream never costs the event itself.
|
|
66
75
|
|
|
67
76
|
Args:
|
|
68
77
|
level: The severity label to stamp on the event.
|
|
@@ -81,8 +90,11 @@ def _log(level: str, message: str, echo: bool, fields: dict[str, object]) -> Non
|
|
|
81
90
|
span = context.current_span()
|
|
82
91
|
event: dict[str, object] | None = None
|
|
83
92
|
if span is not None:
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
try:
|
|
94
|
+
event = build_event(span, level, message, fields=fields, baggage=baggage)
|
|
95
|
+
span.events.append(event)
|
|
96
|
+
except Exception as exc:
|
|
97
|
+
_diag.absorbed("building an in-span log", exc, "the event was lost")
|
|
86
98
|
else:
|
|
87
99
|
try:
|
|
88
100
|
orphan = Span(
|
|
@@ -7,6 +7,7 @@ from collections.abc import Iterable, Mapping, Sequence
|
|
|
7
7
|
from datetime import date, datetime, time
|
|
8
8
|
from decimal import Decimal
|
|
9
9
|
from enum import Enum
|
|
10
|
+
from math import isfinite
|
|
10
11
|
from typing import TYPE_CHECKING, Any
|
|
11
12
|
from uuid import UUID
|
|
12
13
|
|
|
@@ -25,8 +26,21 @@ _LOG10_2_NUM = 30103
|
|
|
25
26
|
_LOG10_2_DEN = 100000
|
|
26
27
|
|
|
27
28
|
_INT_LT = int.__lt__
|
|
29
|
+
_FLOAT_REPR = float.__repr__
|
|
30
|
+
"""``float.__repr__``, unbound, so a subclass cannot divert the placeholder's text.
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
The f-string form ``f"<float: {value}>"`` calls ``format(value, "")``, which is the value's own
|
|
33
|
+
``__str__`` — so a ``float`` subclass chose what the library printed. Measured: a subclass whose
|
|
34
|
+
``__str__`` names its source emitted ``<float: inf from probe-7 (token=sk-live-…)>`` into the event
|
|
35
|
+
stream, and one returning two megabytes emitted all of it against a configured
|
|
36
|
+
``max_value_bytes`` of 8192. Both are the disclosure arch §6 forbids and the ceiling this module
|
|
37
|
+
exists to apply, defeated by the one new rule that handed the decision back to the value.
|
|
38
|
+
|
|
39
|
+
The same reasoning as :data:`_INT_LT` beside it, and as ``_placeholder``'s refusal to use
|
|
40
|
+
``repr()``: what the library writes about a value must be computed by the library.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
_PLAIN_SCALARS: frozenset[type] = frozenset({int, bool})
|
|
30
44
|
|
|
31
45
|
_TEXTLIKE: tuple[type, ...] = (str, bytes, bytearray, memoryview)
|
|
32
46
|
|
|
@@ -222,6 +236,8 @@ class _Coercer:
|
|
|
222
236
|
return self.text(value) # type: ignore[arg-type]
|
|
223
237
|
if kind is int:
|
|
224
238
|
return self.integer(value) # type: ignore[arg-type]
|
|
239
|
+
if kind is float:
|
|
240
|
+
return self.real(value) # type: ignore[arg-type]
|
|
225
241
|
if kind in _PLAIN_SCALARS:
|
|
226
242
|
return value
|
|
227
243
|
if kind is dict:
|
|
@@ -233,6 +249,8 @@ class _Coercer:
|
|
|
233
249
|
member = value.value
|
|
234
250
|
if type(member) is int:
|
|
235
251
|
return self.integer(member)
|
|
252
|
+
if isinstance(member, float) and not isinstance(member, bool):
|
|
253
|
+
return self.real(member)
|
|
236
254
|
if type(member) in _PLAIN_SCALARS or member is None:
|
|
237
255
|
return member
|
|
238
256
|
if isinstance(member, str):
|
|
@@ -243,7 +261,7 @@ class _Coercer:
|
|
|
243
261
|
if isinstance(value, int):
|
|
244
262
|
return self.integer(value)
|
|
245
263
|
if isinstance(value, float):
|
|
246
|
-
return value
|
|
264
|
+
return self.real(value)
|
|
247
265
|
if isinstance(value, (datetime, date, time)):
|
|
248
266
|
return self.text(value.isoformat())
|
|
249
267
|
if isinstance(value, UUID):
|
|
@@ -331,6 +349,12 @@ class _Coercer:
|
|
|
331
349
|
a placeholder, so one hostile key would take every sibling with it, unmarked. ``bool``
|
|
332
350
|
is excluded because ``True`` must render as the key ``"True"``, not ``"1"``.
|
|
333
351
|
|
|
352
|
+
A float key goes through :meth:`real` for the same reason, and its ``text()`` wrap is
|
|
353
|
+
defence in depth rather than load-bearing: ``real`` computes its placeholder through
|
|
354
|
+
:data:`_FLOAT_REPR`, so it cannot hand back a long string. No test distinguishes the wrap
|
|
355
|
+
from its absence, which is recorded here rather than pinned by an assertion that cannot
|
|
356
|
+
fail.
|
|
357
|
+
|
|
334
358
|
Args:
|
|
335
359
|
key: The mapping key, of any type.
|
|
336
360
|
|
|
@@ -345,6 +369,9 @@ class _Coercer:
|
|
|
345
369
|
if isinstance(key, int) and not isinstance(key, bool):
|
|
346
370
|
rendered = self.integer(key)
|
|
347
371
|
return self.text(rendered if isinstance(rendered, str) else str(rendered))
|
|
372
|
+
if isinstance(key, float):
|
|
373
|
+
replaced = self.real(key)
|
|
374
|
+
return self.text(replaced if isinstance(replaced, str) else str(replaced))
|
|
348
375
|
return self.text(str(key))
|
|
349
376
|
|
|
350
377
|
def integer(self, value: int) -> object:
|
|
@@ -379,6 +406,35 @@ class _Coercer:
|
|
|
379
406
|
self.truncated = True
|
|
380
407
|
return f"<int: ~{digits} digits>"
|
|
381
408
|
|
|
409
|
+
def real(self, value: float) -> object:
|
|
410
|
+
"""Replaces a non-finite float, since ``NaN`` and ``Infinity`` are not JSON.
|
|
411
|
+
|
|
412
|
+
``json.dumps`` writes ``NaN``, ``Infinity`` and ``-Infinity`` happily, and RFC 8259
|
|
413
|
+
defines none of them: a strict consumer — Fluent Bit, a Logstash ``json`` codec, Jackson
|
|
414
|
+
behind Elasticsearch — rejects the whole record, with nothing on the library side to see.
|
|
415
|
+
That contradicts ``build_event``'s promise that an event is safe for any sink to
|
|
416
|
+
serialize (SPEC-017).
|
|
417
|
+
|
|
418
|
+
Replaced rather than coerced to ``None`` or ``0.0``, on SPEC-020's reasoning for the
|
|
419
|
+
over-long integer this mirrors: a wrong number is worse than a visibly elided one, and
|
|
420
|
+
the marker says which of the three it was. ``truncated`` is set for the same reason it is
|
|
421
|
+
set everywhere else — a substitution nobody can see is a silent change to the data.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
value: The float to check.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
The float itself when finite, or a ``<float: nan>`` / ``<float: inf>`` /
|
|
428
|
+
``<float: -inf>`` placeholder naming which it was.
|
|
429
|
+
|
|
430
|
+
Raises:
|
|
431
|
+
None.
|
|
432
|
+
"""
|
|
433
|
+
if isfinite(value):
|
|
434
|
+
return value
|
|
435
|
+
self.truncated = True
|
|
436
|
+
return f"<float: {_FLOAT_REPR(value)}>"
|
|
437
|
+
|
|
382
438
|
def text(self, value: str) -> str:
|
|
383
439
|
"""Applies ``max_value_bytes`` to a string, recording whether it fired.
|
|
384
440
|
|
|
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.dev45 → log_foundry-0.10.2.dev47}/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
|