log-foundry 0.10.2.dev129__tar.gz → 1.0.0__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 (59) hide show
  1. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/PKG-INFO +29 -20
  2. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/README.md +28 -19
  3. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/pyproject.toml +6 -4
  4. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/_lifecycle.py +744 -732
  5. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/console.py +1 -1
  6. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/_chunk.py +6 -1
  7. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/_retry.py +68 -0
  8. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/_socket.py +26 -6
  9. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/base.py +7 -2
  10. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/clickhouse.py +35 -7
  11. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/elasticsearch.py +13 -2
  12. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/file.py +49 -29
  13. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/http.py +115 -9
  14. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/logging_sink.py +5 -1
  15. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/logstash.py +44 -10
  16. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/memory.py +5 -2
  17. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/mongodb.py +117 -2
  18. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/nats.py +25 -6
  19. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/postgres.py +22 -8
  20. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/pubsub.py +13 -5
  21. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/rabbitmq.py +53 -1
  22. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/redis.py +16 -3
  23. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/sentry.py +30 -4
  24. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/syslog.py +12 -2
  25. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/worker.py +238 -533
  26. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/LICENSE +0 -0
  27. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/__init__.py +0 -0
  28. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/_diag.py +0 -0
  29. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/_fork.py +0 -0
  30. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/api.py +0 -0
  31. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/config.py +0 -0
  32. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/context.py +0 -0
  33. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/decorator.py +0 -0
  34. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/ids.py +0 -0
  35. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/model.py +0 -0
  36. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/py.typed +0 -0
  37. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/results.py +0 -0
  38. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sanitize.py +0 -0
  39. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/__init__.py +0 -0
  40. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/_batch.py +0 -0
  41. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/_time.py +0 -0
  42. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/callback.py +0 -0
  43. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/datadog.py +0 -0
  44. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/eventhubs.py +0 -0
  45. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/filtering.py +0 -0
  46. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/firehose.py +0 -0
  47. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/honeycomb.py +0 -0
  48. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/kafka.py +0 -0
  49. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/kinesis.py +0 -0
  50. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/loki.py +0 -0
  51. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/multi.py +0 -0
  52. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/newrelic.py +0 -0
  53. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/null.py +0 -0
  54. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/sns.py +0 -0
  55. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/splunk.py +0 -0
  56. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/sqlite.py +0 -0
  57. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/sqs.py +0 -0
  58. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/stdout.py +0 -0
  59. {log_foundry-0.10.2.dev129 → log_foundry-1.0.0}/src/log_foundry/sinks/transform.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: log-foundry
3
- Version: 0.10.2.dev129
3
+ Version: 1.0.0
4
4
  Summary: Generate logs for your console and JSON events for downstream consumption.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -578,7 +578,8 @@ nothing.
578
578
  Wire one up by passing an instance to `configure(sink=...)`; if you never do, the first decorated
579
579
  call falls back to `StdoutSink()`. The **protocol** is a top-level export, alongside
580
580
  `SinkDeliveryError`, `SinkLosses`, `read_losses` and `flush_sink` — the last two being the probes
581
- a wrapper sink needs to ask a child for its losses and to push its client-side buffer. The
581
+ a wrapper sink needs to ask a child for its losses and to push its client-side buffer — plus the
582
+ two default bounds, `DEFAULT_SHUTDOWN_TIMEOUT` and `DEFAULT_SWAP_TIMEOUT`. The
582
583
  **concrete sinks** are not exported, so import each from its own module, e.g.
583
584
  `from log_foundry.sinks.sqs import SQSSink`.
584
585
 
@@ -892,8 +893,8 @@ disconnected, so a sustained outage moves `health().failed_batches` instead of b
892
893
  | `KafkaSink` | `log_foundry.sinks.kafka` | `kafka` | `KafkaSink(topic, *, flush_timeout=10.0, bootstrap_servers="…", key_field="trace_id", producer_config=None)` — `producer_config` is merged **beneath** the sink's own keys, so it reaches `message.timeout.ms` and friends without displacing `bootstrap.servers`; passing it with `producer=` is a `ValueError` |
893
894
  | `RedisStreamsSink` | `log_foundry.sinks.redis` | `redis` | `RedisStreamsSink(stream, *, url=None, maxlen=None)` — `XADD`. `maxlen` caps the stream (`approximate=True`); trimming happens **at Redis**, after delivery, so it is invisible to `health()` — which is why the default is unbounded |
894
895
  | `RedisListSink` | `log_foundry.sinks.redis` | `redis` | `RedisListSink(key, *, url=None, maxlen=None)` — `RPUSH` + `LTRIM` to the newest `maxlen`; same destination-side trimming caveat |
895
- | `RabbitMQSink` | `log_foundry.sinks.rabbitmq` | `amqp` | `RabbitMQSink(*, exchange, routing_key, url=None)` — persistent messages |
896
- | `NATSSink` | `log_foundry.sinks.nats` | `nats` | `NATSSink(subject, *, jetstream=False, servers=None, publish_timeout=10.0, connect_timeout=None, max_reconnect_attempts=None, reconnect_time_wait=None, drain_timeout=None)` — `publish_timeout` bounds one whole `emit` and applies to an injected `client=` too; the four `None` timeouts are forwarded to `nats.connect` only when set, and passing one with `client=` is a `ValueError` |
896
+ | `RabbitMQSink` | `log_foundry.sinks.rabbitmq` | `amqp` | `RabbitMQSink(*, exchange, routing_key, url=None, blocked_connection_timeout=None, socket_timeout=None, stack_timeout=None)` — persistent messages; `pika` leaves `blocked_connection_timeout` unset, so a broker under a memory or disk alarm blocks every publish indefinitely — the sink applies `DEFAULT_BLOCKED_CONNECTION_TIMEOUT` (30 s) unless the URL's query names one, and an explicit keyword overrides the URL. **Read from `pika` 1.4.4 and not executed against a broker** — verify it against yours |
897
+ | `NATSSink` | `log_foundry.sinks.nats` | `nats` | `NATSSink(subject, *, jetstream=False, servers=None, publish_timeout=10.0, connect_timeout=None, max_reconnect_attempts=None, reconnect_time_wait=None, drain_timeout=None)` — a non-positive `max_reconnect_attempts` is a `ValueError` at construction, since `nats-py` retires a server from its pool only under `max_reconnect_attempts > 0` and a non-positive value therefore never returns; `publish_timeout` bounds one whole `emit` and applies to an injected `client=` too; the four `None` timeouts are forwarded to `nats.connect` only when set, and passing one with `client=` is a `ValueError` |
897
898
  | `GooglePubSubSink` | `log_foundry.sinks.pubsub` | `gcp-pubsub` | `GooglePubSubSink(topic)` |
898
899
  | `AzureEventHubsSink` | `log_foundry.sinks.eventhubs` | `azure-eventhubs` | `AzureEventHubsSink(*, connection_str="…", eventhub=None)` |
899
900
 
@@ -908,9 +909,9 @@ Write-only inserts (querying is the downstream tool's job); each needs its own e
908
909
 
909
910
  | Sink | Import from | Extra | Configure |
910
911
  |---|---|---|---|
911
- | `MongoDBSink` | `log_foundry.sinks.mongodb` | `mongo` | `MongoDBSink(*, uri="…", database="…", collection="…")` |
912
+ | `MongoDBSink` | `log_foundry.sinks.mongodb` | `mongo` | `MongoDBSink(*, uri="…", database="…", collection="…", socket_timeout=None, server_selection_timeout=None)` — `pymongo`'s `socketTimeoutMS` default is `None`, so a read that never returns holds the drain thread; the sink applies `DEFAULT_SOCKET_TIMEOUT` (30 s) only when neither the keyword nor the URI query names one |
912
913
  | `PostgresSink` | `log_foundry.sinks.postgres` | `postgres` | `PostgresSink(table, *, dsn="…", create_table=False, connect_timeout=5)` — JSONB `event` column + extracted columns. Reconnects an **owned** connection the server has closed; a `connection=` you inject is never reopened. `connect_timeout` is passed to libpq explicitly, so it **overrides** any `connect_timeout` in your DSN |
913
- | `ClickHouseSink` | `log_foundry.sinks.clickhouse` | `clickhouse` | `ClickHouseSink(table, *, dsn="…", create_table=False)` — MergeTree, columnar insert |
914
+ | `ClickHouseSink` | `log_foundry.sinks.clickhouse` | `clickhouse` | `ClickHouseSink(table, *, dsn="…", create_table=False, chunk_size=1000, send_receive_timeout=None)` — MergeTree, columnar insert; the driver's own 300 s `send_receive_timeout` is finite so it is left alone, and forwarded only when you set it |
914
915
 
915
916
  `PostgresSink` / `ClickHouseSink` default `create_table=False` (you own the schema and indexes); set
916
917
  it `True` for an idempotent `CREATE TABLE IF NOT EXISTS` convenience.
@@ -1103,9 +1104,11 @@ reported. They aggregate different failure populations — one can mean the dest
1103
1104
  data; the other never the destination — the data, or no drain thread at all (SPEC-050) — so a
1104
1105
  single number would hide which fix applies.
1105
1106
 
1106
- `h.sink` is a `SinkLosses`, carrying `dropped` and `failed`, or `None` — `None` when no worker
1107
- exists yet, or when the configured sink reports nothing (`losses()` is optional, and a sink whose
1108
- `losses()` raises reports `None` too). Note the two `dropped` fields count
1107
+ `h.sink` is a `SinkLosses`, carrying `dropped` and `failed`, or `None` — `None` when no sink has
1108
+ been configured at all, and `None` when the configured sink reports nothing (`losses()` is
1109
+ optional, and a sink whose `losses()` raises reports `None` too). It is **not** `None` merely
1110
+ because no worker exists: since SPEC-054 it is answered
1111
+ from the configuration, so it reports on the orphan path too. Note the two `dropped` fields count
1109
1112
  different things: the worker's is backpressure at *its* queue, the sink's is an event that never
1110
1113
  reached the wire. They are separate because the remedies do not overlap — and `sink.dropped` is
1111
1114
  itself two causes, which is why the diagnostic line matters. Most sinks drop only what can never
@@ -1133,10 +1136,13 @@ logging is doing the right thing; it is the *pair* — retired, and still being
1133
1136
  means every log line since the shutdown has gone nowhere. That state used to read as perfectly
1134
1137
  healthy: `stopped_reason` is `None` after a clean shutdown, and the queue simply grows.
1135
1138
 
1136
- `retired`, `orphan_lost` and `in_span_lost` are the fields reported for a process that has **no
1137
- worker at all**. A program that only ever calls `info()`/`error()` outside a span emits
1138
- synchronously and builds no background worker, so the rest describe something that does not exist
1139
- and read zero — which is why that path needs counters of its own. Until it had them, such a process
1139
+ **Six of the twelve fields report for a process that has no worker at all** — `sink`, `retired`,
1140
+ `closing_sinks`, `inherited_sink`, `orphan_lost` and `in_span_lost`. That set is not a list to
1141
+ maintain by hand: it is exactly the fields in `_lifecycle`'s `Health(...)` assembly that are *not*
1142
+ guarded by `counters is None`, and re-reading that call is how to check it. A program that only
1143
+ ever calls `info()`/`error()` outside a span emits synchronously and builds no background worker,
1144
+ so the other six describe something that does not exist and read their empty value — zero for the
1145
+ five counters, `None` for `stopped_reason` — which is why that path needs counters of its own. Until it had them, such a process
1140
1146
  reported `queued=0 dropped=0 failed_batches=0 stopped_reason=None` over total, permanent loss, and
1141
1147
  the only thing that said otherwise was a line on stderr. Its
1142
1148
  `shutdown()` still closes the sink, exactly once and without starting a thread, and `retired` reads
@@ -1411,8 +1417,8 @@ failure lands on whoever caused it rather than on a shared branch. Contributors
1411
1417
  with `scripts/spec-lint.sh`, before pushing.
1412
1418
 
1413
1419
  On a push to `main` the full `ci.yml` matrix still runs, but as the first job of
1414
- [`release.yml`](.github/workflows/release.yml), which `uses:` this same workflow before it is
1415
- allowed to publish — so on main the checks are listed under the *Release* workflow, named
1420
+ [`release.yml`](.github/workflows/release.yml), which `uses:` this same workflow before it would
1421
+ be allowed to publish — so on main the checks are listed under the *Release* workflow, named
1416
1422
  `test / test (py3.12)` and `test / test (py3.13)`. `ci.yml` deliberately carries no `push`
1417
1423
  trigger of its own; it had one, and the result was that every merge ran the identical matrix
1418
1424
  twice. A `v*` tag is gated the same way, by that same reusable call.
@@ -1469,12 +1475,14 @@ sdist and a wheel:
1469
1475
 
1470
1476
  | Trigger | Version built | Published to PyPI as |
1471
1477
  |---|---|---|
1472
- | merge to `main` | `X.Y.Z.devN` | dev pre-release |
1478
+ | merge to `main` | `X.Y.Z.devN` | **nothing, for now** — `publish-dev` is disabled |
1473
1479
  | push tag `vX.Y.Z` | `X.Y.Z` | stable release |
1474
1480
 
1475
- Dev pre-releases keep the upload path exercised on every merge, so a real release is never the
1476
- first time it runs. `pip install log-foundry` still resolves to the latest **stable** version —
1477
- pip ignores pre-releases unless you pass `--pre`.
1481
+ Dev pre-releases **kept** the upload path exercised on every merge, so a real release was never
1482
+ the first time it ran. That property is suspended along with the job: the next `vX.Y.Z` tag is
1483
+ the first attempt at the upload path since `publish-dev` was disabled. `pip install log-foundry`
1484
+ resolves to the latest **stable** version either way — pip ignores pre-releases unless you pass
1485
+ `--pre`.
1478
1486
 
1479
1487
  Cutting a release is one tag:
1480
1488
 
@@ -1491,7 +1499,8 @@ the tagged commit are not seen. A release body cannot be amended once published
1491
1499
  repository has immutable releases — so check the file is there and named for the tag before
1492
1500
  pushing it. [`docs/release-notes/`](docs/release-notes/) holds the ones written so far, and
1493
1501
  [`docs/spec-delivery/RELEASES.md`](docs/spec-delivery/RELEASES.md) records which specs each
1494
- released version carried.
1502
+ version carried — its top row is written in the commit that version's tag is cut from, so it can
1503
+ name a tag that does not exist yet.
1495
1504
 
1496
1505
  Uploads authenticate with PyPI [Trusted Publishing](https://docs.pypi.org/trusted-publishers/)
1497
1506
  (OIDC) through the `pypi` GitHub Environment — there is no API token stored in the repository.
@@ -527,7 +527,8 @@ nothing.
527
527
  Wire one up by passing an instance to `configure(sink=...)`; if you never do, the first decorated
528
528
  call falls back to `StdoutSink()`. The **protocol** is a top-level export, alongside
529
529
  `SinkDeliveryError`, `SinkLosses`, `read_losses` and `flush_sink` — the last two being the probes
530
- a wrapper sink needs to ask a child for its losses and to push its client-side buffer. The
530
+ a wrapper sink needs to ask a child for its losses and to push its client-side buffer — plus the
531
+ two default bounds, `DEFAULT_SHUTDOWN_TIMEOUT` and `DEFAULT_SWAP_TIMEOUT`. The
531
532
  **concrete sinks** are not exported, so import each from its own module, e.g.
532
533
  `from log_foundry.sinks.sqs import SQSSink`.
533
534
 
@@ -841,8 +842,8 @@ disconnected, so a sustained outage moves `health().failed_batches` instead of b
841
842
  | `KafkaSink` | `log_foundry.sinks.kafka` | `kafka` | `KafkaSink(topic, *, flush_timeout=10.0, bootstrap_servers="…", key_field="trace_id", producer_config=None)` — `producer_config` is merged **beneath** the sink's own keys, so it reaches `message.timeout.ms` and friends without displacing `bootstrap.servers`; passing it with `producer=` is a `ValueError` |
842
843
  | `RedisStreamsSink` | `log_foundry.sinks.redis` | `redis` | `RedisStreamsSink(stream, *, url=None, maxlen=None)` — `XADD`. `maxlen` caps the stream (`approximate=True`); trimming happens **at Redis**, after delivery, so it is invisible to `health()` — which is why the default is unbounded |
843
844
  | `RedisListSink` | `log_foundry.sinks.redis` | `redis` | `RedisListSink(key, *, url=None, maxlen=None)` — `RPUSH` + `LTRIM` to the newest `maxlen`; same destination-side trimming caveat |
844
- | `RabbitMQSink` | `log_foundry.sinks.rabbitmq` | `amqp` | `RabbitMQSink(*, exchange, routing_key, url=None)` — persistent messages |
845
- | `NATSSink` | `log_foundry.sinks.nats` | `nats` | `NATSSink(subject, *, jetstream=False, servers=None, publish_timeout=10.0, connect_timeout=None, max_reconnect_attempts=None, reconnect_time_wait=None, drain_timeout=None)` — `publish_timeout` bounds one whole `emit` and applies to an injected `client=` too; the four `None` timeouts are forwarded to `nats.connect` only when set, and passing one with `client=` is a `ValueError` |
845
+ | `RabbitMQSink` | `log_foundry.sinks.rabbitmq` | `amqp` | `RabbitMQSink(*, exchange, routing_key, url=None, blocked_connection_timeout=None, socket_timeout=None, stack_timeout=None)` — persistent messages; `pika` leaves `blocked_connection_timeout` unset, so a broker under a memory or disk alarm blocks every publish indefinitely — the sink applies `DEFAULT_BLOCKED_CONNECTION_TIMEOUT` (30 s) unless the URL's query names one, and an explicit keyword overrides the URL. **Read from `pika` 1.4.4 and not executed against a broker** — verify it against yours |
846
+ | `NATSSink` | `log_foundry.sinks.nats` | `nats` | `NATSSink(subject, *, jetstream=False, servers=None, publish_timeout=10.0, connect_timeout=None, max_reconnect_attempts=None, reconnect_time_wait=None, drain_timeout=None)` — a non-positive `max_reconnect_attempts` is a `ValueError` at construction, since `nats-py` retires a server from its pool only under `max_reconnect_attempts > 0` and a non-positive value therefore never returns; `publish_timeout` bounds one whole `emit` and applies to an injected `client=` too; the four `None` timeouts are forwarded to `nats.connect` only when set, and passing one with `client=` is a `ValueError` |
846
847
  | `GooglePubSubSink` | `log_foundry.sinks.pubsub` | `gcp-pubsub` | `GooglePubSubSink(topic)` |
847
848
  | `AzureEventHubsSink` | `log_foundry.sinks.eventhubs` | `azure-eventhubs` | `AzureEventHubsSink(*, connection_str="…", eventhub=None)` |
848
849
 
@@ -857,9 +858,9 @@ Write-only inserts (querying is the downstream tool's job); each needs its own e
857
858
 
858
859
  | Sink | Import from | Extra | Configure |
859
860
  |---|---|---|---|
860
- | `MongoDBSink` | `log_foundry.sinks.mongodb` | `mongo` | `MongoDBSink(*, uri="…", database="…", collection="…")` |
861
+ | `MongoDBSink` | `log_foundry.sinks.mongodb` | `mongo` | `MongoDBSink(*, uri="…", database="…", collection="…", socket_timeout=None, server_selection_timeout=None)` — `pymongo`'s `socketTimeoutMS` default is `None`, so a read that never returns holds the drain thread; the sink applies `DEFAULT_SOCKET_TIMEOUT` (30 s) only when neither the keyword nor the URI query names one |
861
862
  | `PostgresSink` | `log_foundry.sinks.postgres` | `postgres` | `PostgresSink(table, *, dsn="…", create_table=False, connect_timeout=5)` — JSONB `event` column + extracted columns. Reconnects an **owned** connection the server has closed; a `connection=` you inject is never reopened. `connect_timeout` is passed to libpq explicitly, so it **overrides** any `connect_timeout` in your DSN |
862
- | `ClickHouseSink` | `log_foundry.sinks.clickhouse` | `clickhouse` | `ClickHouseSink(table, *, dsn="…", create_table=False)` — MergeTree, columnar insert |
863
+ | `ClickHouseSink` | `log_foundry.sinks.clickhouse` | `clickhouse` | `ClickHouseSink(table, *, dsn="…", create_table=False, chunk_size=1000, send_receive_timeout=None)` — MergeTree, columnar insert; the driver's own 300 s `send_receive_timeout` is finite so it is left alone, and forwarded only when you set it |
863
864
 
864
865
  `PostgresSink` / `ClickHouseSink` default `create_table=False` (you own the schema and indexes); set
865
866
  it `True` for an idempotent `CREATE TABLE IF NOT EXISTS` convenience.
@@ -1052,9 +1053,11 @@ reported. They aggregate different failure populations — one can mean the dest
1052
1053
  data; the other never the destination — the data, or no drain thread at all (SPEC-050) — so a
1053
1054
  single number would hide which fix applies.
1054
1055
 
1055
- `h.sink` is a `SinkLosses`, carrying `dropped` and `failed`, or `None` — `None` when no worker
1056
- exists yet, or when the configured sink reports nothing (`losses()` is optional, and a sink whose
1057
- `losses()` raises reports `None` too). Note the two `dropped` fields count
1056
+ `h.sink` is a `SinkLosses`, carrying `dropped` and `failed`, or `None` — `None` when no sink has
1057
+ been configured at all, and `None` when the configured sink reports nothing (`losses()` is
1058
+ optional, and a sink whose `losses()` raises reports `None` too). It is **not** `None` merely
1059
+ because no worker exists: since SPEC-054 it is answered
1060
+ from the configuration, so it reports on the orphan path too. Note the two `dropped` fields count
1058
1061
  different things: the worker's is backpressure at *its* queue, the sink's is an event that never
1059
1062
  reached the wire. They are separate because the remedies do not overlap — and `sink.dropped` is
1060
1063
  itself two causes, which is why the diagnostic line matters. Most sinks drop only what can never
@@ -1082,10 +1085,13 @@ logging is doing the right thing; it is the *pair* — retired, and still being
1082
1085
  means every log line since the shutdown has gone nowhere. That state used to read as perfectly
1083
1086
  healthy: `stopped_reason` is `None` after a clean shutdown, and the queue simply grows.
1084
1087
 
1085
- `retired`, `orphan_lost` and `in_span_lost` are the fields reported for a process that has **no
1086
- worker at all**. A program that only ever calls `info()`/`error()` outside a span emits
1087
- synchronously and builds no background worker, so the rest describe something that does not exist
1088
- and read zero — which is why that path needs counters of its own. Until it had them, such a process
1088
+ **Six of the twelve fields report for a process that has no worker at all** — `sink`, `retired`,
1089
+ `closing_sinks`, `inherited_sink`, `orphan_lost` and `in_span_lost`. That set is not a list to
1090
+ maintain by hand: it is exactly the fields in `_lifecycle`'s `Health(...)` assembly that are *not*
1091
+ guarded by `counters is None`, and re-reading that call is how to check it. A program that only
1092
+ ever calls `info()`/`error()` outside a span emits synchronously and builds no background worker,
1093
+ so the other six describe something that does not exist and read their empty value — zero for the
1094
+ five counters, `None` for `stopped_reason` — which is why that path needs counters of its own. Until it had them, such a process
1089
1095
  reported `queued=0 dropped=0 failed_batches=0 stopped_reason=None` over total, permanent loss, and
1090
1096
  the only thing that said otherwise was a line on stderr. Its
1091
1097
  `shutdown()` still closes the sink, exactly once and without starting a thread, and `retired` reads
@@ -1360,8 +1366,8 @@ failure lands on whoever caused it rather than on a shared branch. Contributors
1360
1366
  with `scripts/spec-lint.sh`, before pushing.
1361
1367
 
1362
1368
  On a push to `main` the full `ci.yml` matrix still runs, but as the first job of
1363
- [`release.yml`](.github/workflows/release.yml), which `uses:` this same workflow before it is
1364
- allowed to publish — so on main the checks are listed under the *Release* workflow, named
1369
+ [`release.yml`](.github/workflows/release.yml), which `uses:` this same workflow before it would
1370
+ be allowed to publish — so on main the checks are listed under the *Release* workflow, named
1365
1371
  `test / test (py3.12)` and `test / test (py3.13)`. `ci.yml` deliberately carries no `push`
1366
1372
  trigger of its own; it had one, and the result was that every merge ran the identical matrix
1367
1373
  twice. A `v*` tag is gated the same way, by that same reusable call.
@@ -1418,12 +1424,14 @@ sdist and a wheel:
1418
1424
 
1419
1425
  | Trigger | Version built | Published to PyPI as |
1420
1426
  |---|---|---|
1421
- | merge to `main` | `X.Y.Z.devN` | dev pre-release |
1427
+ | merge to `main` | `X.Y.Z.devN` | **nothing, for now** — `publish-dev` is disabled |
1422
1428
  | push tag `vX.Y.Z` | `X.Y.Z` | stable release |
1423
1429
 
1424
- Dev pre-releases keep the upload path exercised on every merge, so a real release is never the
1425
- first time it runs. `pip install log-foundry` still resolves to the latest **stable** version —
1426
- pip ignores pre-releases unless you pass `--pre`.
1430
+ Dev pre-releases **kept** the upload path exercised on every merge, so a real release was never
1431
+ the first time it ran. That property is suspended along with the job: the next `vX.Y.Z` tag is
1432
+ the first attempt at the upload path since `publish-dev` was disabled. `pip install log-foundry`
1433
+ resolves to the latest **stable** version either way — pip ignores pre-releases unless you pass
1434
+ `--pre`.
1427
1435
 
1428
1436
  Cutting a release is one tag:
1429
1437
 
@@ -1440,7 +1448,8 @@ the tagged commit are not seen. A release body cannot be amended once published
1440
1448
  repository has immutable releases — so check the file is there and named for the tag before
1441
1449
  pushing it. [`docs/release-notes/`](docs/release-notes/) holds the ones written so far, and
1442
1450
  [`docs/spec-delivery/RELEASES.md`](docs/spec-delivery/RELEASES.md) records which specs each
1443
- released version carried.
1451
+ version carried — its top row is written in the commit that version's tag is cut from, so it can
1452
+ name a tag that does not exist yet.
1444
1453
 
1445
1454
  Uploads authenticate with PyPI [Trusted Publishing](https://docs.pypi.org/trusted-publishers/)
1446
1455
  (OIDC) through the `pypi` GitHub Environment — there is no API token stored in the repository.
@@ -30,9 +30,11 @@ dependencies = [
30
30
  classifiers = [
31
31
  # Asserts what the NEXT STABLE TAG will be, not what the current version is. Safe only
32
32
  # because PyPI's default project page renders the latest stable release, which is still
33
- # `v0.10.1` and carries none of this; the artifacts carrying it in the meantime are the
34
- # `.devN` pre-releases that SECURITY.md declares unsupported and that `pip install` does
35
- # not select. Their own per-version pages DO show it, so this is narrow, not free.
33
+ # `v0.10.1` and carries none of this. The artifacts that carried it in the meantime were the
34
+ # `.devN` pre-releases SECURITY.md declares unsupported and `pip install` does not select;
35
+ # their own per-version pages DO show it, so this was narrow rather than free. With
36
+ # `publish-dev` disabled no new artifact carries it at all, which narrows the exposure
37
+ # further — the claim is not more true, it just reaches nobody until the next tag.
36
38
  # Revisit if the next stable tag is not `v1.0.0`: README still says three public shapes
37
39
  # change once, before the API is frozen, and Production/Stable on the default page would
38
40
  # then contradict it.
@@ -74,7 +76,7 @@ keywords = [
74
76
  # vulnerability-reporting channel. The repository is still named `log-forge` — the ORIGINAL name,
75
77
  # which PyPI rejected for the distribution — so these URLs deliberately do not match the package
76
78
  # name. See the note on `name` above before "correcting" them.
77
- version = "0.10.2.dev129"
79
+ version = "1.0.0"
78
80
 
79
81
  [project.urls]
80
82
  Homepage = "https://github.com/agriffi10/log-forge"