Observability
The pool is instrumented with otelsql, with the providers of the application.
Traces
A client span per query, named after the operation (SELECT, INSERT…), with db.system.name, db.operation.name and the query text in db.query.text. Transactions get sql.conn.begin_tx, sql.tx.commit… spans.
The query text holds the SQL with its placeholders, not the arguments. Disable it with sql.observability.query: false.
Metrics
| Metric | Description |
|---|---|
db.client.operation.duration |
Query duration |
db.sql.connection.open, db.sql.connection.max_open, db.sql.connection.wait… |
Pool statistics |
Migrations
sql.Migrate() traces the migration queries under one sql.migrate span, with the number of applied migrations in sql.migrations.applied, and logs database migrated with applied=N. See Migrations.
Your own spans
The query spans are children of the span in the context: pass the request context to the ...Context methods (QueryContext, ExecContext…), and wrap a business operation in a span of your own to group its queries:
func (s *Service) Create(ctx context.Context, title string) (Task, error) { ctx, span := s.telemetry.Tracer().Start(ctx, "tasks.create") defer span.End()
// The INSERT span is a child of tasks.create. _, err := s.db.ExecContext(ctx, "INSERT INTO tasks (id, title) VALUES ($1, $2)", id, title) ...}