first commit
This commit is contained in:
65
docs/persistence/database/db-sqlite/src/otel/otel.md
Normal file
65
docs/persistence/database/db-sqlite/src/otel/otel.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# otel
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
OpenTelemetry instrumentation for `@suaveplan/db-sqlite`. The `withQuerySpan` function wraps any async database operation in a named OTel span, setting `db.system=sqlite` and `db.statement` (sanitised) as span attributes, recording exceptions on failure, and emitting latency and count metrics. It is a no-op when no OTel SDK is registered — the `@opentelemetry/api` no-op provider handles all calls silently.
|
||||
|
||||
`sanitizeSql` strips string literals and numeric values from SQL strings before attaching them to span attributes, preventing PII leakage into telemetry backends. Parameterised queries still record their structure (`SELECT * FROM users WHERE id = ?`) without revealing actual parameter values.
|
||||
|
||||
## Features
|
||||
|
||||
- `withQuerySpan(operationName, rawSql, fn)` — wraps fn in a named span with `db.system=sqlite` and sanitised `db.statement`
|
||||
- `sanitizeSql(sql)` — replaces string literals, quoted identifiers, and bare numbers with `?` placeholders
|
||||
- Histograms: `sqlite.query.duration_ms` (labels: `operation`)
|
||||
- Counters: `sqlite.query.count`, `sqlite.error.count` (labels: `operation`)
|
||||
- `resetOtelInstruments()` — clears the lazy instruments singleton for test isolation
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { withQuerySpan } from "@suaveplan/db-sqlite/otel";
|
||||
|
||||
const rows = await withQuerySpan(
|
||||
"db.query",
|
||||
"SELECT * FROM users WHERE id = 42",
|
||||
async () => sqlite.query("SELECT * FROM users WHERE id = ?").all([42])
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { sanitizeSql, withQuerySpan } from "@suaveplan/db-sqlite/otel";
|
||||
|
||||
// Preview sanitisation before instrumentation
|
||||
const safe = sanitizeSql("SELECT * FROM t WHERE name = 'Alice' AND age = 30");
|
||||
// => "SELECT * FROM t WHERE name = ? AND age = ?"
|
||||
|
||||
const result = await withQuerySpan("db.execute", safe, async () => {
|
||||
sqlite.run("INSERT INTO events VALUES (?, ?)", ["login", 1234567890]);
|
||||
return { rowsAffected: 1 };
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `withQuerySpan<T>(operationName, rawSql, fn): Promise<T>`
|
||||
|
||||
Starts a span named `operationName`, sets `db.system=sqlite` and `db.statement=sanitizeSql(rawSql)`, then calls `fn()`. On success records latency and increments `sqlite.query.count`. On failure records exception, sets `ERROR` status, and increments `sqlite.error.count`. Always ends the span.
|
||||
|
||||
### `sanitizeSql(sql: string): string`
|
||||
|
||||
Replaces single-quoted strings, double-quoted strings, and numeric literals (integer and decimal) with `?`. Safe to call on arbitrary SQL strings — does not parse SQL, only applies a regex substitution.
|
||||
|
||||
### `resetOtelInstruments(): void`
|
||||
|
||||
Clears the lazy OTel instruments singleton. Use in tests so each suite starts with fresh counter baselines. Also called internally by `SqliteAdapter.disconnect()`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The instruments lazy singleton is initialised on the first `withQuerySpan` call rather than at module load to ensure the OTel provider is registered before the meter is obtained. Tests that call `resetOtelInstruments()` between suites prevent counter state from leaking across test files.
|
||||
|
||||
`sanitizeSql` uses a single-pass regex and is intentionally simple — it does not understand SQL syntax, so complex nested strings or edge-case identifiers may not be fully sanitised. This is acceptable for telemetry purposes where approximate sanitisation is sufficient.
|
||||
Reference in New Issue
Block a user