Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<AtomicBool>` 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

Expand Down Expand Up @@ -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
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sqlx-otel"
version = "0.3.0"
version = "0.4.0"
edition = "2024"
authors = ["Borislav Borisov <chmodas@astutebits.com>"]
license = "MIT OR Apache-2.0"
Expand All @@ -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 }
Expand All @@ -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]]
Expand Down
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
10 changes: 6 additions & 4 deletions tests/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()])
Expand Down
10 changes: 6 additions & 4 deletions tests/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()])
Expand Down
Loading