diff --git a/CHANGELOG.md b/CHANGELOG.md index a4e4d83..6126779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.4.0] – 2026-08-08 + +### Changed + +- **Action required:** Upgraded to `opentelemetry` / `opentelemetry-semantic-conventions` `0.32`. Upgrade your own `opentelemetry` and `opentelemetry_sdk` to match, or spans and metrics stop being emitted. + +### Fixed + +- The pool-metrics polling task no longer stops when a single cloned `Pool` is dropped. All clones share one `Arc` shutdown flag, so the first clone to drop was flipping it and silently ending `db.client.connection.*` collection for every surviving clone. The task now stops only once the last clone is dropped, matching what the documentation already implied. + +### Internal + +- Test-only: replaced `ctor` with the `dtor` crate, whose `#[dtor]` macro was split out of `ctor` and removed in `ctor` 1.0, and marked the container-cleanup destructors `unsafe` per `dtor` 1.0's deprecation of the safe form. Bumped `testcontainers` to `0.28`. No effect on the published crate. + +## [0.3.0] – 2026-05-05 ### Added @@ -46,6 +60,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - SQLite backend ([#3](https://github.com/chmodas/sqlx-otel/pull/3)). - Backend-agnostic instrumentation core: `Pool`, `PoolBuilder`, `Transaction`, `PoolConnection`, and the `Executor` trait wiring that emits OpenTelemetry-native spans and metrics following the [database calls and systems](https://opentelemetry.io/docs/specs/semconv/db/) semantic conventions ([#2](https://github.com/chmodas/sqlx-otel/pull/2)). -[Unreleased]: https://github.com/chmodas/sqlx-otel/compare/v0.1.0...HEAD [0.1.0]: https://github.com/chmodas/sqlx-otel/releases/tag/v0.1.0 [0.2.0]: https://github.com/chmodas/sqlx-otel/releases/tag/v0.2.0 +[0.3.0]: https://github.com/chmodas/sqlx-otel/releases/tag/v0.3.0 +[0.4.0]: https://github.com/chmodas/sqlx-otel/releases/tag/v0.4.0 diff --git a/Cargo.toml b/Cargo.toml index f33f1d8..9f438b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sqlx-otel" -version = "0.3.0" +version = "0.4.0" edition = "2024" authors = ["Borislav Borisov "] license = "MIT OR Apache-2.0" @@ -24,8 +24,8 @@ pedantic = "warn" [dependencies] futures = "0.3" -opentelemetry = "0.31" -opentelemetry-semantic-conventions = { version = "0.31", features = ["semconv_experimental"] } +opentelemetry = "0.32" +opentelemetry-semantic-conventions = { version = "0.32", features = ["semconv_experimental"] } sqlx = { version = "0.8", default-features = false } async-std = { version = "1", optional = true } tokio = { version = "1", features = ["rt", "time"], optional = true } @@ -42,12 +42,12 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dev-dependencies] -ctor = "0.10" -opentelemetry_sdk = { version = "0.31", features = ["testing", "rt-tokio"] } +dtor = "1" +opentelemetry_sdk = { version = "0.32", features = ["testing", "rt-tokio"] } proptest = "1" serial_test = "3" sqlx = { version = "0.8", features = ["macros", "sqlite", "postgres", "mysql", "runtime-tokio"] } -testcontainers = "0.27" +testcontainers = "0.28" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [[example]] diff --git a/README.md b/README.md index 19d8642..434155b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The wrapper talks to the [`opentelemetry`](https://docs.rs/opentelemetry) API di ```toml [dependencies] -sqlx-otel = { version = "0.2.0", features = ["postgres", "runtime-tokio"] } +sqlx-otel = { version = "0.4.0", features = ["postgres", "runtime-tokio"] } ``` ```rust @@ -221,9 +221,28 @@ The first four are recorded inline on every `acquire()` / connection drop – no ## Compatibility -- **MSRV:** Rust **1.85.0**. -- **SQLx:** `0.8.x`. -- **OpenTelemetry:** `0.31.x`. +| sqlx-otel | sqlx | opentelemetry | MSRV | +| --------- | ------- | ------------- | -------- | +| `0.4.x` | `0.8.x` | `0.32.x` | `1.85.0` | +| `0.3.x` | `0.8.x` | `0.31.x` | `1.85.0` | +| `0.2.x` | `0.8.x` | `0.31.x` | `1.85.0` | +| `0.1.x` | `0.8.x` | `0.31.x` | `1.85.0` | + +Pick the row matching the `sqlx` and `opentelemetry` versions your application already uses. Older releases stay on crates.io indefinitely and are never yanked, so pinning to an earlier row is a supported way to stay put. + +## Versioning policy + +`sqlx` and `opentelemetry` are both pre-1.0, so Cargo compares their *minor* version for compatibility: `0.31` and `0.32` are separate ranges that never unify, whether or not the release actually changed anything. If your application's versions drift from the ones sqlx-otel was built against, Cargo resolves both copies into the build graph, and the two libraries fail differently. + +**`sqlx` fails at compile time.** Its types are part of this crate's public API, and types from semver-incompatible releases are distinct types. A `Pool` from `sqlx` `0.9` will not go into a wrapper built against `sqlx` `0.8`. + +**`opentelemetry` fails silently at runtime.** It coordinates through process-global state, so two copies means two registries. Your application installs its provider in one; the wrapper emits into the other. Spans and metrics disappear, with no error and no warning. + +Hence: + +- Either library bumping its minor triggers a minor release here. +- Bugs are not backported. Fixes ship in the next release. +- MSRV tracks [sqlx's MSRV policy](https://github.com/transact-rs/sqlx/blob/main/FAQ.md#MSRV). ## License diff --git a/tests/mysql.rs b/tests/mysql.rs index 92269ec..ab8e133 100644 --- a/tests/mysql.rs +++ b/tests/mysql.rs @@ -63,11 +63,13 @@ async fn shared_container() -> &'static SharedContainer { /// Stop and remove the shared container at process exit. Required because the /// `ContainerAsync` value lives in a `'static` (`CONTAINER`), so the language never -/// runs its `Drop`. Using `ctor::dtor` schedules a synchronous shell-out to +/// runs its `Drop`. Using `dtor::dtor` schedules a synchronous shell-out to /// `docker rm -f` that fires after `main` returns – equivalent to the per-test -/// RAII cleanup that existed before the shared-container refactor (commit c29f995). -#[ctor::dtor] -fn drop_container() { +/// RAII cleanup that existed before the shared-container refactor (commit c29f995). The fn is +/// `unsafe` because `dtor` runs it outside the Rust runtime, where most std facilities carry no +/// guarantees; spawning a subprocess and reading a `OnceLock` is safe in practice. +#[dtor::dtor] +unsafe fn drop_container() { if let Some(id) = CONTAINER_ID.get() { let _ = std::process::Command::new("docker") .args(["rm", "-f", id.as_str()]) diff --git a/tests/postgres.rs b/tests/postgres.rs index d3f4e0d..0380ba3 100644 --- a/tests/postgres.rs +++ b/tests/postgres.rs @@ -63,11 +63,13 @@ async fn shared_container() -> &'static SharedContainer { /// Stop and remove the shared container at process exit. Required because the /// `ContainerAsync` value lives in a `'static` (`CONTAINER`), so the language never -/// runs its `Drop`. Using `ctor::dtor` schedules a synchronous shell-out to +/// runs its `Drop`. Using `dtor::dtor` schedules a synchronous shell-out to /// `docker rm -f` that fires after `main` returns – equivalent to the per-test -/// RAII cleanup that existed before the shared-container refactor (commit c29f995). -#[ctor::dtor] -fn drop_container() { +/// RAII cleanup that existed before the shared-container refactor (commit c29f995). The fn is +/// `unsafe` because `dtor` runs it outside the Rust runtime, where most std facilities carry no +/// guarantees; spawning a subprocess and reading a `OnceLock` is safe in practice. +#[dtor::dtor] +unsafe fn drop_container() { if let Some(id) = CONTAINER_ID.get() { let _ = std::process::Command::new("docker") .args(["rm", "-f", id.as_str()])