sql

package
v0.3.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package sql provides an instrumented database/sql driver wrapper with automatic OpenTelemetry tracing and metrics.

Features

  • OpenTelemetry tracing with span per query
  • Prometheus-compatible metrics for query latency
  • Automatic query operation extraction (SELECT, INSERT, etc.)
  • Query sanitization for secure logging
  • Full compatibility with database/sql interface

Quick Start

Open a database connection with instrumentation:

import sentinelsql "github.com/kroma-labs/sentinel-go/sql"

db, err := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithDBSystem("postgresql"),
    sentinelsql.WithDBName("myapp"),
)
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// Use like standard *sql.DB
rows, err := db.QueryContext(ctx, "SELECT * FROM users")

Driver Registration

For more control, register a wrapped driver:

driver := sentinelsql.WrapDriver(pq.Driver{},
    sentinelsql.WithDBSystem("postgresql"),
)
sql.Register("postgres-instrumented", driver)

db, _ := sql.Open("postgres-instrumented", dsn)

Configuration Options

Common options for customization:

db, _ := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithDBSystem("postgresql"),     // Required: database type
    sentinelsql.WithDBName("users_db"),         // Database name
    sentinelsql.WithInstanceName("primary"),    // Connection identifier
    sentinelsql.WithQuerySanitizer(sanitizer),  // Mask sensitive values
    sentinelsql.WithDisableQuery(true),         // Omit queries from spans
)

Query Sanitization

Use DefaultQuerySanitizer to mask sensitive values:

// Input:  "SELECT * FROM users WHERE id = 123"
// Output: "SELECT * FROM users WHERE id = ?"

db, _ := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithQuerySanitizer(sentinelsql.DefaultQuerySanitizer),
)

Observability

The wrapper automatically emits:

Traces:

  • Span per query with operation name
  • Attributes: db.system, db.name, db.statement, db.operation

Metrics:

  • db.client.query.duration (histogram by operation)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultQuerySanitizer

func DefaultQuerySanitizer(query string) string

DefaultQuerySanitizer is a basic query sanitizer that replaces literal values with placeholders to prevent sensitive data from appearing in traces.

What it sanitizes:

  • String literals: 'john' → '?'
  • Numeric literals: 123, 45.67 → ?
  • Hex literals: 0xDEADBEEF → ?

Example:

DefaultQuerySanitizer("SELECT * FROM users WHERE id = 123")
// returns "SELECT * FROM users WHERE id = ?"

DefaultQuerySanitizer("SELECT * FROM users WHERE name = 'john'")
// returns "SELECT * FROM users WHERE name = '?'"

Note: This is a simple regex-based implementation. For production use with complex queries, consider using a proper SQL parser.

func Open

func Open(driverName, dsn string, opts ...Option) (*sql.DB, error)

Open wraps the specified driver and opens a database connection. It returns a standard *sql.DB that is fully compatible with database/sql. All operations will be automatically traced and metered.

The driver is registered once per (driverName, options) combination. Subsequent calls with the same driver name and options reuse the registration.

Example:

db, err := sentinelsql.Open("postgres",
    "postgres://user:pass@localhost/mydb?sslmode=disable",
    sentinelsql.WithDBSystem("postgresql"),
    sentinelsql.WithDBName("mydb"),
)

func RecordPoolMetrics

func RecordPoolMetrics(db *sql.DB, meter metric.Meter, attrs ...attribute.KeyValue) error

RecordPoolMetrics registers connection pool metrics for a database.

This function attempts to automatically detect the attributes used in sentinelsql.Open(). If the driver is not a Sentinel-wrapped driver, it will fall back to using only the provided attributes.

You can optionally provide additional attributes here, which will be merged with the auto-detected ones.

Example:

db, _ := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithDBSystem("postgresql"),
    sentinelsql.WithDBName("mydb"),
)

// Register pool metrics (attributes are auto-detected!)
err := sentinelsql.RecordPoolMetrics(db, otel.GetMeterProvider().Meter("myapp"))

func Register

func Register(name string, d driver.Driver, opts ...Option)

Register registers a wrapped driver with the given name. This is useful when you want to control the driver name explicitly.

Example:

sentinelsql.Register("otel-postgres", pgDriver,
    sentinelsql.WithDBSystem("postgresql"),
)
db, _ := sql.Open("otel-postgres", dsn)

func WrapDriver

func WrapDriver(d driver.Driver, opts ...Option) driver.Driver

WrapDriver wraps a driver.Driver with OpenTelemetry instrumentation. Use this when you need more control over driver registration.

Example:

wrapped := sentinelsql.WrapDriver(myDriver,
    sentinelsql.WithDBSystem("postgresql"),
)
sql.Register("my-otel-driver", wrapped)

Types

type DriverConn added in v0.2.1

DriverConn represents a database connection for testing.

type DriverConnector added in v0.2.1

type DriverConnector interface {
	Connect(ctx context.Context) (driver.Conn, error)
	Driver() driver.Driver
}

DriverConnector represents a driver connector for testing.

type DriverResult added in v0.2.1

type DriverResult interface {
	LastInsertId() (int64, error)
	RowsAffected() (int64, error)
}

DriverResult represents the result of an Exec query.

type DriverRows added in v0.2.1

type DriverRows interface {
	Columns() []string
	Close() error
	Next(dest []driver.Value) error
}

DriverRows represents rows returned from a query.

type DriverStmt added in v0.2.1

type DriverStmt interface {
	driver.Stmt
	driver.StmtExecContext
	driver.StmtQueryContext
}

DriverStmt represents a prepared statement for testing.

type DriverTx added in v0.2.1

type DriverTx interface {
	Commit() error
	Rollback() error
}

DriverTx represents a database transaction for testing.

type Option

type Option func(*config)

Option configures the instrumentation.

func WithDBName

func WithDBName(name string) Option

WithDBName sets the database name being accessed. This is added as the "db.name" attribute on all spans.

Example:

// Connecting to "users_db" database
db, _ := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithDBName("users_db"),
)

func WithDBSystem

func WithDBSystem(system string) Option

WithDBSystem sets the database system identifier (DBMS product). This is added as the "db.system" attribute on all spans.

Common values:

  • "postgresql" - PostgreSQL
  • "mysql" - MySQL
  • "sqlite" - SQLite
  • "mssql" - Microsoft SQL Server
  • "oracle" - Oracle Database

Example:

db, _ := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithDBSystem("postgresql"),
)

func WithDisableQuery

func WithDisableQuery() Option

WithDisableQuery disables recording of SQL queries in spans entirely. Use this when queries may contain sensitive data and you cannot use a sanitizer.

When enabled, the "db.statement" attribute will not be added to spans, but "db.operation" (SELECT, INSERT, etc.) will still be recorded.

Example:

db, _ := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithDisableQuery(),
)

func WithInstanceName

func WithInstanceName(name string) Option

WithInstanceName sets an identifier for this specific database connection. This is added as the "db.instance" attribute on all spans.

Use this to distinguish between multiple connections to the SAME database, such as:

  • Primary/replica setups: "primary", "replica-1", "replica-2"
  • Read/write splits: "read", "write"
  • Sharded databases: "shard-0", "shard-1"
  • Connection pools: "pool-a", "pool-b"

This makes it easy to filter and analyze traces by connection type.

Example - Primary/Replica setup:

// Primary connection for writes
writerDB, _ := sentinelsql.Open("postgres", primaryDSN,
    sentinelsql.WithDBSystem("postgresql"),
    sentinelsql.WithDBName("myapp"),
    sentinelsql.WithInstanceName("primary"),
)

// Replica connection for reads
readerDB, _ := sentinelsql.Open("postgres", replicaDSN,
    sentinelsql.WithDBSystem("postgresql"),
    sentinelsql.WithDBName("myapp"),
    sentinelsql.WithInstanceName("replica"),
)

In your traces, you'll see:

Span: SELECT FROM users
├── db.system: postgresql
├── db.name: myapp
└── db.instance: replica  ← Easy to identify!

func WithMeterProvider

func WithMeterProvider(mp metric.MeterProvider) Option

WithMeterProvider sets a custom meter provider. If not called, the global provider from otel.GetMeterProvider() is used.

Example:

mp := sdkmetric.NewMeterProvider(...)
db, _ := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithMeterProvider(mp),
)

func WithQuerySanitizer

func WithQuerySanitizer(fn func(string) string) Option

WithQuerySanitizer sets a custom query sanitizer function. The sanitizer receives the raw SQL query and should return a sanitized version with sensitive data (like literals) replaced with placeholders.

Use DefaultQuerySanitizer for a basic implementation that replaces string literals, numbers, and hex values with "?" placeholders.

Example:

db, _ := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithQuerySanitizer(sentinelsql.DefaultQuerySanitizer),
)
// Query: "SELECT * FROM users WHERE id = 123"
// Recorded as: "SELECT * FROM users WHERE id = ?"

func WithTracerProvider

func WithTracerProvider(tp trace.TracerProvider) Option

WithTracerProvider sets a custom tracer provider. If not called, the global provider from otel.GetTracerProvider() is used.

Example:

tp := sdktrace.NewTracerProvider(...)
db, _ := sentinelsql.Open("postgres", dsn,
    sentinelsql.WithTracerProvider(tp),
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL