log-foundry 0.5.1.dev9__tar.gz → 0.5.1.dev10__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 (52) hide show
  1. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/PKG-INFO +1 -1
  2. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/pyproject.toml +1 -1
  3. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/model.py +10 -4
  4. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sanitize.py +8 -3
  5. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/multi.py +5 -1
  6. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/worker.py +9 -3
  7. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/LICENSE +0 -0
  8. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/README.md +0 -0
  9. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/__init__.py +0 -0
  10. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/api.py +0 -0
  11. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/config.py +0 -0
  12. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/console.py +0 -0
  13. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/context.py +0 -0
  14. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/decorator.py +0 -0
  15. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/ids.py +0 -0
  16. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/py.typed +0 -0
  17. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/__init__.py +0 -0
  18. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/_chunk.py +0 -0
  19. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/_socket.py +0 -0
  20. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/_time.py +0 -0
  21. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/base.py +0 -0
  22. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/callback.py +0 -0
  23. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/clickhouse.py +0 -0
  24. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/datadog.py +0 -0
  25. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/elasticsearch.py +0 -0
  26. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/eventhubs.py +0 -0
  27. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/file.py +0 -0
  28. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/filtering.py +0 -0
  29. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/firehose.py +0 -0
  30. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/honeycomb.py +0 -0
  31. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/http.py +0 -0
  32. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/kafka.py +0 -0
  33. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/kinesis.py +0 -0
  34. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/logging_sink.py +0 -0
  35. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/logstash.py +0 -0
  36. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/loki.py +0 -0
  37. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/mongodb.py +0 -0
  38. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/nats.py +0 -0
  39. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/newrelic.py +0 -0
  40. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/postgres.py +0 -0
  41. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/pubsub.py +0 -0
  42. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/rabbitmq.py +0 -0
  43. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/redis.py +0 -0
  44. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/sentry.py +0 -0
  45. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/sns.py +0 -0
  46. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/splunk.py +0 -0
  47. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/sqlite.py +0 -0
  48. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/sqs.py +0 -0
  49. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/stdout.py +0 -0
  50. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/syslog.py +0 -0
  51. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/src/log_foundry/sinks/transform.py +0 -0
  52. {log_foundry-0.5.1.dev9 → log_foundry-0.5.1.dev10}/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.5.1.dev9
3
+ Version: 0.5.1.dev10
4
4
  Summary: Generate logs for your console and JSON events for downstream consumption.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -20,7 +20,7 @@ dependencies = [
20
20
  ]
21
21
 
22
22
  # Optional features. Install with: pip install log-foundry[aws]
23
- version = "0.5.1.dev9"
23
+ version = "0.5.1.dev10"
24
24
 
25
25
  [project.optional-dependencies]
26
26
  aws = ["boto3>=1.34"] # SQSSink, SNSSink, KinesisSink, FirehoseSink
@@ -70,8 +70,13 @@ def build_event(
70
70
  → per-call ``fields`` (arch §5.1). Later sources win on a key conflict.
71
71
 
72
72
  The merged mapping is coerced and size-bounded here (SPEC-017 FR-001/FR-002), which is what
73
- makes every sink's bare ``json.dumps`` safe without any of them changing. ``message`` is
74
- bounded too: it is a base field, but unlike the other eleven it is caller-supplied free text.
73
+ makes every sink's bare ``json.dumps`` safe without any of them changing.
74
+
75
+ ``message`` and ``function`` are bounded too. They are base fields, but unlike the other ten
76
+ both are caller-supplied text: ``message`` directly, and ``function`` via ``@trace(name=...)``
77
+ or — on the orphan path, where ``api`` names the standalone span after the message itself —
78
+ from the same unbounded string. Leaving either out would keep ``info(huge_string)`` unbounded,
79
+ which is the hole this ceiling exists to close.
75
80
  """
76
81
  from log_foundry.config import get_config
77
82
  from log_foundry.ids import new_log_id
@@ -80,6 +85,7 @@ def build_event(
80
85
  merged: dict[str, object] = {**cfg.defaults, **span.defaults, **baggage, **fields}
81
86
  safe, clipped = sanitize_fields(merged, cfg=cfg)
82
87
  bounded_message, message_clipped = truncate_str(message, cfg.max_value_bytes)
88
+ bounded_function, function_clipped = truncate_str(span.name, cfg.max_value_bytes)
83
89
  event: dict[str, object] = {
84
90
  "timestamp": _iso_now(),
85
91
  "level": level,
@@ -88,13 +94,13 @@ def build_event(
88
94
  "span_id": span.span_id,
89
95
  "parent_span_id": span.parent_span_id,
90
96
  "log_id": new_log_id(),
91
- "function": span.name,
97
+ "function": bounded_function,
92
98
  "service": cfg.service,
93
99
  "version": cfg.version,
94
100
  "env": cfg.env,
95
101
  }
96
102
  # Before ``fields`` so it reads ahead of the payload blob in a rendered log line.
97
- if clipped or message_clipped:
103
+ if clipped or message_clipped or function_clipped:
98
104
  event[_TRUNCATED] = True
99
105
  event["fields"] = safe
100
106
  return event
@@ -254,8 +254,9 @@ class _Coercer:
254
254
  def coerce(value: object, *, cfg: Config) -> object:
255
255
  """Return a JSON-serializable, size-bounded equivalent of ``value``. Never raises.
256
256
 
257
- Use :func:`sanitize_fields` for a whole event mappingit reports whether a ceiling fired,
258
- which this cannot.
257
+ ``value`` is treated as a field value, i.e. depth 0 the same level it would occupy inside
258
+ an event's ``fields``. Use :func:`sanitize_fields` for a whole event mapping; it reports
259
+ whether a ceiling fired, which this cannot.
259
260
  """
260
261
  return _Coercer(cfg).value(value, 0)
261
262
 
@@ -271,7 +272,11 @@ def sanitize_fields(
271
272
  """
272
273
  coercer = _Coercer(cfg)
273
274
  try:
274
- result = coercer.mapping(fields, 0)
275
+ # ``-1`` so the *values* of the top-level mapping sit at depth 0: ``fields`` is the event's
276
+ # payload container, not a level of nesting the caller chose. Otherwise ``max_depth=1``
277
+ # would replace every field value with ``<depth limit>`` and emit a uniformly empty event,
278
+ # which is exactly what FR-006's validation exists to prevent.
279
+ result = coercer.mapping(fields, -1)
275
280
  except Exception: # noqa: BLE001 — belt and braces: `_Coercer.value` is already total, but
276
281
  return {}, True # a hostile top-level mapping must not reach the caller either.
277
282
  if not isinstance(result, dict):
@@ -27,7 +27,11 @@ class MultiSink:
27
27
  """A :class:`~log_foundry.sinks.base.Sink` that forwards each batch to every child sink.
28
28
 
29
29
  Attributes:
30
- failed: Count of child ``emit``/``close`` calls that raised and were isolated.
30
+ failed: Count of child ``emit``/``close`` **calls** that raised. Note this counts calls,
31
+ not batches: since total failure re-raises (SPEC-017 FR-004), the worker retries the
32
+ same batch, so one batch against an all-down fan-out of *n* children increments this
33
+ by *n* per attempt — ``n * (max_retries + 1)`` in total, with a stderr line each.
34
+ That is the visible cost of the loss no longer being silent.
31
35
  """
32
36
 
33
37
  def __init__(self, *sinks: Sink) -> None:
@@ -121,9 +121,15 @@ class Worker:
121
121
  # submitters. Lines may therefore interleave out of order under concurrency; the
122
122
  # counts they carry are still exact.
123
123
  if total == 1 or total % _DROP_WARN_EVERY == 0:
124
- sys.stderr.write(
125
- f"log-foundry: log queue full, dropped {total} submission(s) so far\n"
126
- )
124
+ try:
125
+ sys.stderr.write(
126
+ f"log-foundry: log queue full, dropped {total} submission(s) so far\n"
127
+ )
128
+ except Exception: # noqa: BLE001 — submit() runs on the *caller's* thread, so an
129
+ # unwritable stderr (closed fd, broken pipe, daemonized process) would raise
130
+ # straight into the app. A diagnostic about dropped logs must never itself be
131
+ # the reason a decorated function fails. The counter is already recorded.
132
+ pass
127
133
 
128
134
  def health(self) -> Health:
129
135
  """Snapshot the delivery counters (SPEC-017 FR-005). Never raises.