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 ¶
- func DefaultQuerySanitizer(query string) string
- func Open(driverName, dsn string, opts ...Option) (*sql.DB, error)
- func RecordPoolMetrics(db *sql.DB, meter metric.Meter, attrs ...attribute.KeyValue) error
- func Register(name string, d driver.Driver, opts ...Option)
- func WrapDriver(d driver.Driver, opts ...Option) driver.Driver
- type DriverConn
- type DriverConnector
- type DriverResult
- type DriverRows
- type DriverStmt
- type DriverTx
- type Option
- func WithDBName(name string) Option
- func WithDBSystem(system string) Option
- func WithDisableQuery() Option
- func WithInstanceName(name string) Option
- func WithMeterProvider(mp metric.MeterProvider) Option
- func WithQuerySanitizer(fn func(string) string) Option
- func WithTracerProvider(tp trace.TracerProvider) Option
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultQuerySanitizer ¶
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 ¶
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 ¶
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 ¶
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 ¶
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
type DriverConn interface {
driver.Conn
driver.ConnPrepareContext
driver.ConnBeginTx
driver.ExecerContext
driver.QueryerContext
driver.Pinger
}
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
DriverResult represents the result of an Exec query.
type DriverRows ¶ added in v0.2.1
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 Option ¶
type Option func(*config)
Option configures the instrumentation.
func WithDBName ¶
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 ¶
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 ¶
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 ¶
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),
)