log-foundry 0.7.2.dev2__tar.gz → 0.7.2.dev4__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.
Files changed (53) hide show
  1. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/PKG-INFO +7 -1
  2. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/README.md +6 -0
  3. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/pyproject.toml +2 -1
  4. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/config.py +10 -1
  5. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sanitize.py +23 -2
  6. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/worker.py +17 -1
  7. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/LICENSE +0 -0
  8. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/__init__.py +0 -0
  9. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/api.py +0 -0
  10. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/console.py +0 -0
  11. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/context.py +0 -0
  12. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/decorator.py +0 -0
  13. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/ids.py +0 -0
  14. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/model.py +0 -0
  15. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/py.typed +0 -0
  16. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/__init__.py +0 -0
  17. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/_batch.py +0 -0
  18. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/_chunk.py +0 -0
  19. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/_socket.py +0 -0
  20. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/_time.py +0 -0
  21. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/base.py +0 -0
  22. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/callback.py +0 -0
  23. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/clickhouse.py +0 -0
  24. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/datadog.py +0 -0
  25. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/elasticsearch.py +0 -0
  26. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/eventhubs.py +0 -0
  27. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/file.py +0 -0
  28. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/filtering.py +0 -0
  29. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/firehose.py +0 -0
  30. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/honeycomb.py +0 -0
  31. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/http.py +0 -0
  32. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/kafka.py +0 -0
  33. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/kinesis.py +0 -0
  34. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/logging_sink.py +0 -0
  35. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/logstash.py +0 -0
  36. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/loki.py +0 -0
  37. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/mongodb.py +0 -0
  38. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/multi.py +0 -0
  39. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/nats.py +0 -0
  40. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/newrelic.py +0 -0
  41. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/postgres.py +0 -0
  42. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/pubsub.py +0 -0
  43. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/rabbitmq.py +0 -0
  44. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/redis.py +0 -0
  45. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/sentry.py +0 -0
  46. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/sns.py +0 -0
  47. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/splunk.py +0 -0
  48. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/sqlite.py +0 -0
  49. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/sqs.py +0 -0
  50. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/stdout.py +0 -0
  51. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/syslog.py +0 -0
  52. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/transform.py +0 -0
  53. {log_foundry-0.7.2.dev2 → log_foundry-0.7.2.dev4}/src/log_foundry/sinks/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: log-foundry
3
- Version: 0.7.2.dev2
3
+ Version: 0.7.2.dev4
4
4
  Summary: Generate logs for your console and JSON events for downstream consumption.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -783,6 +783,12 @@ the default configuration the interpreter's limit — not `max_value_bytes` —
783
783
  unlikely to meet it deliberately; `int.from_bytes(blob, "big")` over a couple of kilobytes gets
784
784
  there. Any ceiling firing sets `truncated: true` on the event.
785
785
 
786
+ `max_value_bytes` therefore carries two units: **UTF-8 bytes** for a string, **rendered decimal
787
+ length** (sign included) for an integer. They coincide for ASCII digits, and one ceiling for "how
788
+ big may a single value get" was preferred to a second config key. Note that all four ceilings
789
+ bound each *value* — an event of many bounded values can still be large; see
790
+ [Known constraints](docs/architecture.md#known-constraints).
791
+
786
792
  ## Development
787
793
 
788
794
  ```bash
@@ -747,6 +747,12 @@ the default configuration the interpreter's limit — not `max_value_bytes` —
747
747
  unlikely to meet it deliberately; `int.from_bytes(blob, "big")` over a couple of kilobytes gets
748
748
  there. Any ceiling firing sets `truncated: true` on the event.
749
749
 
750
+ `max_value_bytes` therefore carries two units: **UTF-8 bytes** for a string, **rendered decimal
751
+ length** (sign included) for an integer. They coincide for ASCII digits, and one ceiling for "how
752
+ big may a single value get" was preferred to a second config key. Note that all four ceilings
753
+ bound each *value* — an event of many bounded values can still be large; see
754
+ [Known constraints](docs/architecture.md#known-constraints).
755
+
750
756
  ## Development
751
757
 
752
758
  ```bash
@@ -20,7 +20,7 @@ dependencies = [
20
20
  ]
21
21
 
22
22
  # Optional features. Install with: pip install log-foundry[aws]
23
- version = "0.7.2.dev2"
23
+ version = "0.7.2.dev4"
24
24
 
25
25
  [project.optional-dependencies]
26
26
  aws = ["boto3>=1.34"] # SQSSink, SNSSink, KinesisSink, FirehoseSink
@@ -58,6 +58,7 @@ exclude = [
58
58
 
59
59
  # Development-only dependencies (not shipped to consumers).
60
60
  # Install with: poetry install --with dev
61
+
61
62
  [tool.poetry.group.dev.dependencies]
62
63
  pytest = "^9.0"
63
64
  pytest-asyncio = "^1.4.0" # for testing async @trace (guide Phase 8)
@@ -24,6 +24,15 @@ class Config:
24
24
  The four ``max_*`` ceilings bound every event payload (SPEC-017 FR-002). Defaults are set so
25
25
  that the overwhelming majority of events are untouched; they exist to stop *one* pathological
26
26
  value getting a whole event rejected by a sink's hard limit.
27
+
28
+ ``max_value_bytes`` carries **two units**, deliberately (SPEC-020, recorded by SPEC-021): a
29
+ string is measured in UTF-8 bytes, an integer in the decimal length it renders as, sign
30
+ included. They coincide for ASCII digits, and one ceiling covering "how big may a single
31
+ value get" beat a second config key for a distinction almost no one configures. An integer is
32
+ also bounded by ``sys.get_int_max_str_digits()`` whenever that is lower, since a longer one
33
+ cannot be rendered at all.
34
+
35
+ They bound each *value*, not the event as a whole — see arch §13 Known Constraints.
27
36
  """
28
37
 
29
38
  service: str = "unknown"
@@ -31,7 +40,7 @@ class Config:
31
40
  env: str = "dev"
32
41
  sink: Sink | None = None
33
42
  defaults: dict[str, object] = field(default_factory=dict)
34
- max_value_bytes: int = 8192 # per str value, UTF-8 bytes
43
+ max_value_bytes: int = 8192 # per value: UTF-8 bytes for a str, rendered digits for an int
35
44
  max_stack_bytes: int = 32768 # error.stack only — legitimately long, and worth keeping
36
45
  max_keys: int = 256 # per mapping / sequence
37
46
  max_depth: int = 8 # nesting levels
@@ -55,6 +55,12 @@ _DEPTH_LIMIT = "<depth limit>"
55
55
  _LOG10_2_NUM = 30103
56
56
  _LOG10_2_DEN = 100000
57
57
 
58
+ # ``int.__lt__`` bound once, for the sign test in :meth:`_Coercer.integer` (SPEC-021 FR-003). The
59
+ # unbound slot cannot be diverted by an ``int`` subclass the way ``value < 0`` can, but reaching
60
+ # it through ``int.__lt__`` costs a global plus an attribute lookup on a per-value hot path;
61
+ # binding it here recovers about a third of that, the same idiom as the constants above.
62
+ _INT_LT = int.__lt__
63
+
58
64
  # Exact-type membership, deliberately not ``isinstance``. ``IntEnum``/``StrEnum`` members *are*
59
65
  # ``int``/``str`` instances, so an isinstance check would pass the enum member itself through and
60
66
  # hand a sink an ``Enum`` where a plain value was promised. Exact typing lets them fall to the
@@ -74,6 +80,11 @@ def _int_digit_ceiling(max_value_bytes: int) -> int:
74
80
  raises ``ValueError``, which ``json.dumps`` inherits. A configured ceiling above that cannot be
75
81
  honoured — rendering such an integer is the very thing that raises — so the interpreter's limit
76
82
  wins whenever it is lower. A limit of ``0`` means the interpreter imposes none.
83
+
84
+ The caller measures a negative value's rendered length, sign included, against this; the
85
+ interpreter's own limit counts digits only. A negative integer sitting exactly on the
86
+ interpreter bound is therefore replaced rather than rendered — one value at the far edge, in
87
+ the direction this module always errs (SPEC-021 FR-003).
77
88
  """
78
89
  limit = sys.get_int_max_str_digits()
79
90
  return max_value_bytes if limit <= 0 else min(max_value_bytes, limit)
@@ -278,7 +289,11 @@ class _Coercer:
278
289
  The size test is ``bit_length()``, never ``len(str(value))``: converting an over-long
279
290
  integer to a string raises the very ``ValueError`` this bound exists to prevent, so the
280
291
  obvious check would move the crash rather than remove it (FR-002). ``bit_length()`` is
281
- O(1), total, and ignores the sign, so ``n`` and ``-n`` are bounded identically.
292
+ O(1), total, and ignores the sign so the minus sign is added back explicitly, since the
293
+ ceiling measures what the value *renders* as and ``-10**9`` renders as eleven bytes
294
+ (SPEC-021 FR-003). The placeholder still names the digit count: a sign is not a digit.
295
+ The sign is read through an *unbound* ``int.__lt__``, which an ``int`` subclass cannot
296
+ divert; ``bit_length()`` stays the one trusted call, as below.
282
297
 
283
298
  An over-long integer is *replaced*, not clipped. Dropping digits would silently change the
284
299
  value, and a wrong number is worse than a visibly elided one — so this reuses the
@@ -291,7 +306,13 @@ class _Coercer:
291
306
  ``type()`` check to catch only a value engineered to lie about itself.
292
307
  """
293
308
  digits = value.bit_length() * _LOG10_2_NUM // _LOG10_2_DEN + 1
294
- if digits <= _int_digit_ceiling(self._cfg.max_value_bytes):
309
+ # ``_INT_LT`` (unbound ``int.__lt__``), never ``value < 0``: on an ``int`` *subclass* the
310
+ # operator dispatches to user code, which can raise — and a raise here is caught up in
311
+ # :meth:`value`, replacing the whole enclosing mapping and taking every sibling key with
312
+ # it, which is precisely what :meth:`key` was written to prevent. The unbound call reads
313
+ # the underlying integer and cannot be overridden.
314
+ rendered = digits + 1 if _INT_LT(value, 0) else digits
315
+ if rendered <= _int_digit_ceiling(self._cfg.max_value_bytes):
295
316
  return value
296
317
  self.truncated = True
297
318
  return f"<int: ~{digits} digits>"
@@ -308,14 +308,30 @@ class Worker:
308
308
  must not be able to ride on it. The exception's *type* is reported and its message is
309
309
  not — a sink's exception text can carry event data, and arch §6 keeps caller data out of
310
310
  places it was not asked for (the same rule behind ``sanitize``'s type-name placeholder).
311
+
312
+ The count reports what was *in hand* and what was still *queued behind it* (SPEC-021
313
+ FR-002). Held alone under-reads the loss: nothing will drain the queue either, so an
314
+ operator reading "1 undrained event-list(s)" could conclude far less was lost than was.
315
+
316
+ The queued figure is "items", not "event-lists", and says so: like ``Health.queued`` it is
317
+ read without stopping the world, so it counts any internal flush/shutdown marker sitting
318
+ alongside real submissions, and a producer thread can add to the queue between the death
319
+ and the read. It is a floor on what was lost, which is the useful direction.
311
320
  """
312
321
  name = type(exc).__name__
313
322
  with self._lock:
314
323
  self.stopped_reason = name
324
+ try:
325
+ # In its own guard, and after the record: ``qsize()`` is not guaranteed on every
326
+ # platform's queue, and a diagnostic must not be the reason the diagnosis is lost.
327
+ queued: object = self._queue.qsize()
328
+ except Exception:
329
+ queued = "?"
315
330
  try:
316
331
  sys.stderr.write(
317
332
  f"log-foundry: worker thread stopped on {name}; {undrained} undrained "
318
- f"event-list(s), nothing further will be delivered\n"
333
+ f"event-list(s) held and {queued} queued item(s) undelivered, nothing further "
334
+ f"will be delivered\n"
319
335
  )
320
336
  except Exception: # best-effort: the record above is what an operator reads.
321
337
  pass