1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
import type Database from "better-sqlite3";
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
const TRACER_NAME = "@karakeep/db";
function getOperationType(sql: string): string {
return sql.trimStart().split(/\s/, 1)[0]?.toUpperCase() ?? "UNKNOWN";
}
/**
* Instruments a better-sqlite3 Database instance with OpenTelemetry tracing.
*
* Wraps `prepare()` so that every `run()`, `get()`, and `all()` call on
* the returned Statement produces an OTel span with db.system, db.statement,
* and db.operation attributes.
*
* The instrumentation is a no-op when no OTel TracerProvider is registered
* (i.e. when tracing is disabled), following standard OTel conventions.
*/
export function instrumentDatabase(
sqlite: Database.Database,
): Database.Database {
const tracer = trace.getTracer(TRACER_NAME);
const origPrepare = sqlite.prepare.bind(sqlite);
sqlite.prepare = function (sql: string) {
const stmt = origPrepare(sql);
const operation = getOperationType(sql);
const spanName = `db.${operation.toLowerCase()}`;
for (const method of ["run", "get", "all"] as const) {
type QueryFn = (...args: unknown[]) => unknown;
const original = (stmt[method] as QueryFn).bind(stmt);
(stmt[method] as QueryFn) = function (...args: unknown[]) {
const span = tracer.startSpan(spanName, {
kind: SpanKind.CLIENT,
attributes: {
"db.system": "sqlite",
"db.statement": sql,
"db.operation": operation,
},
});
try {
const result = original(...args);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error instanceof Error ? error.message : String(error),
});
span.recordException(
error instanceof Error ? error : new Error(String(error)),
);
throw error;
} finally {
span.end();
}
};
}
return stmt;
} as typeof sqlite.prepare;
return sqlite;
}
|