diff --git a/apps/event-queue/Dockerfile b/apps/event-queue/Dockerfile index 4bd9b8f65..c1cb3b616 100644 --- a/apps/event-queue/Dockerfile +++ b/apps/event-queue/Dockerfile @@ -52,4 +52,4 @@ EXPOSE 3124 WORKDIR apps/event-queue/dist/ -CMD ["node", "index.js"] \ No newline at end of file +CMD ["node", "-r", "./instrumentation-node.js", "index.js"] \ No newline at end of file diff --git a/apps/event-queue/package.json b/apps/event-queue/package.json index 0df0ac1e5..25b6c4b1c 100644 --- a/apps/event-queue/package.json +++ b/apps/event-queue/package.json @@ -19,6 +19,15 @@ "@ctrlplane/validators": "workspace:*", "@octokit/auth-app": "catalog:", "@octokit/rest": "catalog:", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/auto-instrumentations-node": "^0.52.1", + "@opentelemetry/exporter-logs-otlp-http": "^0.54.2", + "@opentelemetry/exporter-trace-otlp-http": "^0.54.2", + "@opentelemetry/resources": "^1.27.0", + "@opentelemetry/sdk-logs": "^0.54.2", + "@opentelemetry/sdk-node": "^0.54.2", + "@opentelemetry/sdk-trace-base": "^1.27.0", + "@opentelemetry/semantic-conventions": "^1.27.0", "@t3-oss/env-core": "catalog:", "dotenv": "^16.4.5", "kafkajs": "^2.2.4", diff --git a/apps/event-queue/src/events/resources.ts b/apps/event-queue/src/events/resources.ts index 1b493444d..752570070 100644 --- a/apps/event-queue/src/events/resources.ts +++ b/apps/event-queue/src/events/resources.ts @@ -1,5 +1,8 @@ import type * as schema from "@ctrlplane/db/schema"; import type { Event } from "@ctrlplane/events"; +import { trace } from "@opentelemetry/api"; + +import { makeWithSpan } from "@ctrlplane/logger"; import type { Handler } from "."; import { OperationPipeline } from "../workspace/pipeline.js"; @@ -16,12 +19,19 @@ const getResourceWithDates = (resource: schema.Resource) => { return { ...resource, createdAt, updatedAt, lockedAt, deletedAt }; }; -export const newResource: Handler = async (event) => { - const ws = await WorkspaceManager.getOrLoad(event.workspaceId); - if (ws == null) return; - const resource = getResourceWithDates(event.payload); - await OperationPipeline.update(ws).resource(resource).dispatch(); -}; +const newResourceTracer = trace.getTracer("new-resource"); +const withSpan = makeWithSpan(newResourceTracer); + +export const newResource: Handler = withSpan( + "new-resource", + async (span, event) => { + span.setAttribute("resource.id", event.payload.id); + const ws = await WorkspaceManager.getOrLoad(event.workspaceId); + if (ws == null) return; + const resource = getResourceWithDates(event.payload); + await OperationPipeline.update(ws).resource(resource).dispatch(); + }, +); export const updatedResource: Handler = async ( event, diff --git a/apps/event-queue/src/instrumentation-node.ts b/apps/event-queue/src/instrumentation-node.ts new file mode 100644 index 000000000..39ce06796 --- /dev/null +++ b/apps/event-queue/src/instrumentation-node.ts @@ -0,0 +1,65 @@ +import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; +import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { Resource } from "@opentelemetry/resources"; +import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"; +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { + AlwaysOnSampler, + BatchSpanProcessor, +} from "@opentelemetry/sdk-trace-base"; +import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions"; + +const sdk = new NodeSDK({ + resource: new Resource({ + [ATTR_SERVICE_NAME]: "ctrlplane/event-queue", + }), + spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter()) as any], + logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())], + instrumentations: [ + getNodeAutoInstrumentations({ + "@opentelemetry/instrumentation-fs": { + enabled: false, + }, + "@opentelemetry/instrumentation-net": { + enabled: false, + }, + "@opentelemetry/instrumentation-dns": { + enabled: false, + }, + "@opentelemetry/instrumentation-http": { + enabled: true, + }, + "@opentelemetry/instrumentation-pg": { + enabled: true, + enhancedDatabaseReporting: true, + addSqlCommenterCommentToQueries: true, + }, + "@opentelemetry/instrumentation-ioredis": { + enabled: true, + }, + "@opentelemetry/instrumentation-winston": { + enabled: true, + logHook: (span, record) => { + record["resource.service.name"] = "ctrlplane/event-queue"; + }, + }, + }), + ], + sampler: new AlwaysOnSampler(), +}); + +try { + sdk.start(); + console.log("Tracing initialized"); +} catch (error) { + console.error("Error initializing tracing", error); +} + +process.on("SIGTERM", () => { + sdk + .shutdown() + .then(() => console.log("Tracing shutdown")) + .catch((error) => console.error("Error shutting down tracing", error)) + .finally(() => process.exit(0)); +}); diff --git a/apps/event-queue/src/instrumentation.ts b/apps/event-queue/src/instrumentation.ts new file mode 100644 index 000000000..3240e018a --- /dev/null +++ b/apps/event-queue/src/instrumentation.ts @@ -0,0 +1,4 @@ +export async function register() { + if (process.env.NODE_ENV === "production") + await import("./instrumentation-node.js"); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed3d0a10d..2ba352172 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,6 +157,33 @@ importers: '@octokit/rest': specifier: 'catalog:' version: 20.1.1 + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/auto-instrumentations-node': + specifier: ^0.52.1 + version: 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': + specifier: ^0.54.2 + version: 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: ^0.54.2 + version: 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': + specifier: ^1.27.0 + version: 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': + specifier: ^0.54.2 + version: 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': + specifier: ^0.54.2 + version: 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: ^1.27.0 + version: 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': + specifier: ^1.27.0 + version: 1.34.0 '@t3-oss/env-core': specifier: 'catalog:' version: 0.11.1(typescript@5.8.2)(zod@3.24.2) @@ -17626,7 +17653,7 @@ snapshots: '@types/pg-pool@2.0.6': dependencies: - '@types/pg': 8.6.1 + '@types/pg': 8.15.4 '@types/pg@8.15.4': dependencies: