Skip to content

Commit cd78310

Browse files
committed
Enable otel
1 parent 074c6d1 commit cd78310

File tree

8 files changed

+224
-1
lines changed

8 files changed

+224
-1
lines changed

witness/bun.lock

Lines changed: 109 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

witness/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
"typescript": "^5"
2626
},
2727
"dependencies": {
28+
"@opentelemetry/api": "^1.9.0",
29+
"@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
30+
"@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
31+
"@opentelemetry/sdk-node": "^0.213.0",
32+
"@opentelemetry/semantic-conventions": "^1.40.0",
2833
"drizzle-orm": "^0.45.1",
2934
"elysia": "^1.4.27",
3035
"graphile-worker": "^0.16.6",

witness/src/entrypoints/api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import { initTelemetry, shutdownTelemetry } from "../telemetry/index.ts";
12
import { makeWorkerUtils } from "graphile-worker";
23
import { loadApiConfig } from "../config/env.ts";
34
import { createDb, closeDb } from "../db/client.ts";
45
import { createApiServer } from "../api/server.ts";
56
import { createHealthServer } from "../api/health.server.ts";
67

78
async function main() {
9+
initTelemetry();
810
const config = loadApiConfig();
911
const db = createDb(config.databaseUrl);
1012
const workerUtils = await makeWorkerUtils({ connectionString: config.databaseUrl });
@@ -36,6 +38,7 @@ async function main() {
3638
}
3739

3840
await closeDb();
41+
await shutdownTelemetry();
3942
process.exit(0);
4043
}
4144

witness/src/entrypoints/worker.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { initTelemetry, shutdownTelemetry } from "../telemetry/index.ts";
12
import { loadWorkerConfig } from "../config/env.ts";
23
import { createEnvSigner } from "../core/signing.ts";
34
import { createDb, closeDb } from "../db/client.ts";
45
import { setupWorker } from "../worker/setup.ts";
56

67
async function main() {
8+
initTelemetry();
79
const config = loadWorkerConfig();
810
const signer = createEnvSigner(config.witnessPrivateKey);
911
const db = createDb(config.databaseUrl);
@@ -28,6 +30,7 @@ async function main() {
2830
}
2931

3032
await closeDb();
33+
await shutdownTelemetry();
3134
process.exit(0);
3235
}
3336

witness/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { initTelemetry, shutdownTelemetry } from "./telemetry/index.ts";
12
import { loadConfig } from "./config/env.ts";
23
import { createEnvSigner } from "./core/signing.ts";
34
import { createDb, closeDb } from "./db/client.ts";
@@ -8,6 +9,7 @@ import { createApiServer } from "./api/server.ts";
89
import path from "path";
910

1011
async function main() {
12+
initTelemetry();
1113
const config = loadConfig();
1214

1315
const signer = createEnvSigner(config.witnessPrivateKey);
@@ -54,6 +56,7 @@ async function main() {
5456

5557
clearTimeout(timeout);
5658
await closeDb();
59+
await shutdownTelemetry();
5760
process.exit(0);
5861
}
5962

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { trace, metrics, SpanStatusCode } from "@opentelemetry/api";
2+
import type { RpcClient } from "./client.ts";
3+
4+
const tracer = trace.getTracer("vow-witness");
5+
const meter = metrics.getMeter("vow-witness");
6+
7+
const durationHistogram = meter.createHistogram("vow.rpc.duration", {
8+
description: "RPC call duration in milliseconds",
9+
unit: "ms",
10+
});
11+
12+
const errorsCounter = meter.createCounter("vow.rpc.errors", {
13+
description: "RPC call error count",
14+
});
15+
16+
type RpcMethod = "getBlock" | "getLogs" | "getBlockNumber";
17+
18+
function wrapMethod<T extends (...args: any[]) => Promise<any>>(
19+
method: T,
20+
name: RpcMethod,
21+
attrs: { "rpc.url": string; "chain.id": number }
22+
): T {
23+
return (async (...args: any[]) => {
24+
const spanAttrs = { "rpc.method": name, ...attrs };
25+
const start = Date.now();
26+
27+
return tracer.startActiveSpan(`rpc.${name}`, { attributes: spanAttrs }, async (span) => {
28+
try {
29+
const result = await method(...args);
30+
durationHistogram.record(Date.now() - start, spanAttrs);
31+
return result;
32+
} catch (err: any) {
33+
durationHistogram.record(Date.now() - start, spanAttrs);
34+
errorsCounter.add(1, { ...spanAttrs, "error.type": err?.constructor?.name ?? "Error" });
35+
span.recordException(err);
36+
span.setStatus({ code: SpanStatusCode.ERROR });
37+
throw err;
38+
} finally {
39+
span.end();
40+
}
41+
});
42+
}) as T;
43+
}
44+
45+
export function instrumentRpcClient(
46+
client: RpcClient,
47+
attrs: { url: string; chainId: number }
48+
): RpcClient {
49+
const commonAttrs = { "rpc.url": attrs.url, "chain.id": attrs.chainId };
50+
return {
51+
getBlock: wrapMethod(client.getBlock.bind(client), "getBlock", commonAttrs),
52+
getLogs: wrapMethod(client.getLogs.bind(client), "getLogs", commonAttrs),
53+
getBlockNumber: wrapMethod(client.getBlockNumber.bind(client), "getBlockNumber", commonAttrs),
54+
};
55+
}

witness/src/telemetry/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { NodeSDK } from "@opentelemetry/sdk-node";
2+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
3+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
4+
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
5+
6+
let sdk: NodeSDK | undefined;
7+
8+
export function initTelemetry(): void {
9+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return;
10+
11+
sdk = new NodeSDK({
12+
traceExporter: new OTLPTraceExporter(),
13+
metricReader: new PeriodicExportingMetricReader({
14+
exporter: new OTLPMetricExporter(),
15+
exportIntervalMillis: 15_000,
16+
}),
17+
});
18+
19+
sdk.start();
20+
console.log("OpenTelemetry SDK started");
21+
}
22+
23+
export async function shutdownTelemetry(): Promise<void> {
24+
if (!sdk) return;
25+
try {
26+
await sdk.shutdown();
27+
} catch (e) {
28+
console.error("OpenTelemetry shutdown error:", e);
29+
}
30+
}

witness/src/worker/index-block.task.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { type Task } from "graphile-worker";
22
import { eq, sql } from "drizzle-orm";
33
import { type Hex } from "viem";
4+
import { trace, SpanStatusCode } from "@opentelemetry/api";
45
import { createRpcClient, type RpcClient } from "../rpc/client.ts";
6+
import { instrumentRpcClient } from "../rpc/instrumented-client.ts";
57
import { fetchBlockConsistent, type ConsistentBlockResult } from "../rpc/consistency.ts";
68
import { buildMerkleTree, ZERO_HASH } from "../core/merkle.ts";
79
import { chains, rpcs, indexedBlocks, indexedEvents } from "../db/schema.ts";
@@ -30,6 +32,12 @@ export function createIndexBlockTask(
3032
const { chainId, blockNumber } = payload as IndexBlockPayload;
3133
const blockNumberBigInt = BigInt(blockNumber);
3234

35+
const tracer = trace.getTracer("vow-witness");
36+
const span = tracer.startSpan("index-block", {
37+
attributes: { "chain.id": chainId, "block.number": blockNumber },
38+
});
39+
40+
try {
3341
// Fetch RPC URLs for this chain
3442
const rpcRows = await db
3543
.select({ url: rpcs.url })
@@ -43,7 +51,7 @@ export function createIndexBlockTask(
4351
}
4452

4553
const rpcClients = rpcRows.map((row) =>
46-
createRpcClient(row.url)
54+
instrumentRpcClient(createRpcClient(row.url), { url: row.url, chainId })
4755
);
4856

4957
// Fetch and validate block data from all RPCs
@@ -138,5 +146,12 @@ export function createIndexBlockTask(
138146
})
139147
.where(eq(chains.chainId, chainId));
140148
});
149+
} catch (err: any) {
150+
span.recordException(err);
151+
span.setStatus({ code: SpanStatusCode.ERROR });
152+
throw err;
153+
} finally {
154+
span.end();
155+
}
141156
};
142157
}

0 commit comments

Comments
 (0)