# Rbtz.PostgrexDisconnectTracker
[](https://github.com/tinyrbtz/postgrex_disconnect_tracker/actions/workflows/ci.yml)
[](https://hex.pm/packages/rbtz_postgrex_disconnect_tracker)
[](https://hex.pm/packages/rbtz_postgrex_disconnect_tracker)
[](http://doge.mit-license.org)
Pins Postgrex disconnection errors to the ExUnit test that caused them.
When a test leaks a Postgrex connection — e.g. a spawned task outlives the test, a sandbox owner exits mid-query, or a driver crash hits the pool — the error logged by Postgrex looks like this:
```
[error] Postgrex.Protocol (#PID<0.512.0>) disconnected: ** (DBConnection.ConnectionError) client #PID<0.1234.0> exited
```
The message names the **client PID**, but that PID no longer exists and has no connection back to the test suite. You're left grepping for which test touched the affected code. In a CI run of a thousand tests that can be a bad afternoon.
This library attaches a telemetry handler to your repo and an OTP `:logger` handler, and maintains an ETS map from query-executing PIDs (and their ancestors) to the test that started them. When Postgrex logs a disconnect, the handler parses the client PID, looks up the owning test, and prints an annotated error like:
```
=== POSTGREX DISCONNECTION ===
Test: MyApp.AccountsTest - test registers a user
Client PID: #PID<0.1234.0>
Stacktrace: ...
Error: ...
==============================
```
## Why / when to use this
**Use it when:**
- You've seen intermittent `Postgrex.Protocol ... disconnected` errors in CI and don't know which test is responsible.
- You're hunting a leaked connection, a background task that outlives its test, or a misconfigured `Ecto.Adapters.SQL.Sandbox` owner.
- A CI run is flaky and you suspect one bad test is knocking out the connection pool and poisoning later tests.
**Don't use it when:**
- You aren't seeing Postgrex disconnect errors. The tracker adds a telemetry handler on every query — the overhead is small but non-zero, and the output is noise unless disconnects are actually happening.
- You're running production code. This is a **test-only** utility — only attach it from `test_helper.exs`.
It's a diagnostic aid, not a fix. Once it points you at the offending test, you still need to patch that test (close the connection, wait for the task, re-scope the sandbox, etc.).
## Installation
Add `rbtz_postgrex_disconnect_tracker` to your `mix.exs` test deps:
```elixir
def deps do
[
{:rbtz_postgrex_disconnect_tracker, "~> 0.1", only: :test}
]
end
```
Run `mix deps.get`.
## Setup (recommended)
One call in `test/test_helper.exs`, before `ExUnit.start/1`:
```elixir
Rbtz.PostgrexDisconnectTracker.setup(telemetry_event: [:my_app, :repo, :query])
ExUnit.start()
```
`:telemetry_event` is the Ecto query telemetry event for your repo — usually `[:<your_otp_app>, :repo, :query]`. Ecto fires it on every query; the tracker uses it to learn which process is running queries for which test.
Then in your `DataCase` (or whichever `ExUnit.CaseTemplate` sets up the sandbox), register the current test at the start of each `setup`:
```elixir
defmodule MyApp.DataCase do
use ExUnit.CaseTemplate
setup tags do
Rbtz.PostgrexDisconnectTracker.register_test("#{tags.module} - #{tags.test}")
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(MyApp.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
:ok
end
end
```
That's it. When a disconnect is logged, you'll see the test name in the output.
## Setup (individual functions)
If you need to wire the pieces yourself — for example, to attach the logger on a different subset of tests, or to hold off on the telemetry handler until a suite-level setup — call them separately instead of `setup/1`:
```elixir
Rbtz.PostgrexDisconnectTracker.Tracker.init(telemetry_event: [:my_app, :repo, :query])
Rbtz.PostgrexDisconnectTracker.Logger.attach()
```
`setup/1` is exactly equivalent to these two calls in this order.
## License
MIT. See [LICENSE](LICENSE).