Skip to content

feat: add QueryTracer interface for SQL statement tracing (#1716)#1748

Draft
ljluestc wants to merge 1 commit intogo-sql-driver:masterfrom
ljluestc:feat/query-tracer
Draft

feat: add QueryTracer interface for SQL statement tracing (#1716)#1748
ljluestc wants to merge 1 commit intogo-sql-driver:masterfrom
ljluestc:feat/query-tracer

Conversation

@ljluestc
Copy link

The existing Logger interface (cfg.Logger) only logs internal driver
errors (e.g., authentication failures, broken connections). There is no
way to observe actual SQL statements, parameters, errors, or timing
without wrapping database/sql externally.

Ref: #1716

Solution

A new QueryTracer interface with two methods:

type QueryTracer interface {
    StartTrace(ctx context.Context, query string, args []driver.NamedValue) context.Context
    EndTrace(ctx context.Context, err error, duration time.Duration)
}
  • StartTrace is called before the query executes, receiving the query
    and arguments. Returns a new context (e.g., for OpenTelemetry span propagation).
  • EndTrace is called after execution completes with the error and wall-clock duration.

Usage

type myTracer struct{}

func (t *myTracer) StartTrace(ctx context.Context, query string, args []driver.NamedValue) context.Context {
    log.Printf("[SQL] %s args=%v", query, args)
    return ctx
}

func (t *myTracer) EndTrace(ctx context.Context, err error, d time.Duration) {
    if err != nil {
        log.Printf("[SQL ERR] %v (%s)", err, d)
    } else {
        log.Printf("[SQL OK] %s", d)
    }
}

cfg, _ := mysql.ParseDSN(dsn)
cfg.Apply(mysql.WithTracer(&myTracer{}))
connector, _ := mysql.NewFile(cfg)
db := sql.OpenDB(connector)

Instrumented code paths

  • (*mysql.Conn).ExecContextconnection.go
  • (*mysql.Conn).QueryContextconnection.go
  • (*mysql.Conn).PrepareContextconnection.go
  • (*mysql.Stmt).ExecContextconnection.go
  • (*mysql.Stmt).QueryContextconnection.go

When no tracer is configured, the cost is a single nil check per query (zero allocations).

Files changed

  • tracer.go (new) — QueryTracer interface and tracerQuery helper method
  • tracer_test.go (new) — 5 unit tests
  • dsn.go — added tracer field to Config, added WithTracer option function
  • connection.go — instrumented ExecContext, QueryContext, PrepareContext, Stmt.ExecContext, Stmt.QueryContext
  • stmt.go — added queryStr field to mysqlStmt for tracing prepared statements

How to test

1. Run the new unit tests

go test -v -run "TestTraceQuery|TestWithTracer" ./...

Expected:

=== RUN   TestTraceQuery_WithTracer
--- PASS: TestTraceQuery_WithTracer (0.00s)
=== RUN   TestTraceQuery_ContextFlows
--- PASS: TestTraceQuery_ContextFlows (0.00s)
=== RUN   TestTraceQuery_WithError
--- PASS: TestTraceQuery_WithError (0.00s)
=== RUN   TestTraceQuery_NilTracer
--- PASS: TestTraceQuery_NilTracer (0.00s)
=== RUN   TestWithTracerOption
--- PASS: TestWithTracerOption (0.00s)
PASS

2. Run the full test suite

go test -short ./...
go vet ./...

3. Manual integration test

With a local MySQL instance:

cfg, _ := mysql.ParseDSN("user:pass@tcp(127.0.0.1:3306)/testdb")
cfg.Apply(mysql.WithTracer(&myTracer{}))
connector, _ := mysql.NewConnector(cfg)
db := sql.OpenDB(connector)

db.ExecContext(ctx, "CREATE TABLE IF NOT EXISTS t (id INT)")
db.QueryContext(ctx, "SELECT * FROM t WHERE id = ?", 1)

stmt, _ := db.PrepareContext(ctx, "INSERT INTO t VALUES (?)")
stmt.ExecContext(ctx, 42)

Each call prints the SQL and timing via the tracer.

Checklist

  • Code compiles (go build ./...)
  • Created tests which fail without the change
  • All tests passing (go test -short ./...)
  • go vet passes
  • Extended README / documentation if needed
  • Added to AUTHORS file

…ver#1716)

Add a QueryTracer interface that allows users to trace SQL query execution
for logging, metrics, or distributed tracing. Inspired by pgx's tracelog.

- QueryTracer.TraceQueryStart is called before query execution with the
  query string and arguments, returning a context for span propagation.
- QueryTracer.TraceQueryEnd is called after completion with the error
  and wall-clock duration.

Instrumented paths: ExecContext, QueryContext, PrepareContext,
mysqlStmt.ExecContext, mysqlStmt.QueryContext.

The tracer is configured via the WithTracer functional option.
When no tracer is set, the overhead is a single nil check per query.

Co-Authored-By: Warp <agent@warp.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant