feat: instrument registration/event routes + metrics - #73
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR introduces OpenTelemetry-based observability for the API service by adding an OTel SDK bootstrap that runs before the Fastify app starts, alongside dependency and runtime entrypoint updates.
Changes:
- Add OpenTelemetry NodeSDK setup (OTLP trace exporter + auto-instrumentations + Fastify instrumentation) and load it via a new
bootstrapentrypoint. - Update API runtime entrypoints (
npm start,npm dev, DockerCMD) to use the new bootstrap file. - Refactor the Clerk webhook route definition (no functional change intended).
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks new OpenTelemetry/Fastify OTel dependencies and transitive packages. |
| apps/api/src/telemetry/sdk.ts | Initializes the OpenTelemetry NodeSDK and instrumentations. |
| apps/api/src/telemetry/config.ts | Defines telemetry service/exporter configuration constants. |
| apps/api/src/modules/webhooks/clerk.ts | Restructures Clerk webhook route handler. |
| apps/api/src/bootstrap.ts | New entrypoint to start telemetry before importing the app. |
| apps/api/package.json | Switch start/dev scripts to use the bootstrap entrypoint and add OTel deps. |
| apps/api/Dockerfile | Updates container startup command to run the bootstrap entrypoint. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
apps/api/src/telemetry/config.ts:3
- Telemetry configuration is hardcoded in source control (service name/version and OTLP endpoint). This makes deployments difficult and can accidentally point production at localhost. Prefer reading from environment variables with sensible defaults.
export const OTEL_SERVICE_NAME = "events.compsoc.api";
export const OTEL_SERVICE_VERSION = "1.0.0";
export const OTEL_BASE_URL = "http://127.0.0.1:4318";
apps/api/src/telemetry/sdk.ts:13
deployment.environment.nameis currently hardcoded to "development", so traces from other environments will be mislabeled. Prefer deriving this fromprocess.env.NODE_ENV(or a dedicated OTEL env var) with a default.
resource: resourceFromAttributes({
"service.name": OTEL_SERVICE_NAME,
"service.version": OTEL_SERVICE_VERSION,
"deployment.environment.name": "development",
}),
apps/api/src/telemetry/sdk.ts:37
- The telemetry SDK is started as a fire-and-forget side effect. If startup fails, the error is currently unhandled, and bootstrap continues. Consider awaiting startup and surfacing failures so the process doesn’t run without telemetry unintentionally.
sdk.start();
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.
Suppressed comments (6)
Previously missed (1) — in code that hasn't changed since the last review.
apps/api/src/telemetry/config.ts:3
- Telemetry config is hard-coded (service version, exporter URL). This will likely be wrong in non-dev environments and makes container deployments harder. Prefer reading these from environment variables with sane defaults.
export const OTEL_SERVICE_NAME = "events.compsoc.api";
export const OTEL_SERVICE_VERSION = "1.0.0";
export const OTEL_BASE_URL = "http://127.0.0.1:4318";
apps/api/src/modules/webhooks/clerk.ts:29
- Spelling in the comment: "recieved" -> "received".
// User data, which recieved from clerk on registration
apps/api/src/telemetry/sdk.ts:13
deployment.environment.nameis hard-coded to "development", so traces from staging/prod will be mislabeled unless this file is edited per environment. Derive it from env (e.g.OTEL_ENVIRONMENT/NODE_ENV) with a default.
resource: resourceFromAttributes({
"service.name": OTEL_SERVICE_NAME,
"service.version": OTEL_SERVICE_VERSION,
"deployment.environment.name": "development",
}),
apps/api/src/telemetry/sdk.ts:26
- The HTTP instrumentation only ignores requests where
request.url === "/health". If the health endpoint is called with a query string (e.g./health?full=1) it will still be traced. Consider a prefix match instead.
"@opentelemetry/instrumentation-http": {
ignoreIncomingRequestHook: (request) => request.url === "/health",
},
apps/api/src/telemetry/sdk.ts:37
NodeSDK.start()is async; calling it without awaiting/catching means startup failures can become unhandled rejections, and the app may import/load modules before instrumentation is fully initialized (missing early spans). It also never shuts down, so spans may be dropped on SIGTERM/SIGINT. Consider awaiting start (top-level await is OK in ESM) and registering shutdown handlers.
sdk.start();
apps/api/src/telemetry/sdk.ts:18
- The PR title mentions "metrics", but this SDK setup only configures a trace exporter (no
metricReader/metrics exporter is set up). If metrics are intended, you'll need to add a metrics exporter/reader; otherwise consider updating the PR title to avoid confusion.
const sdk = new NodeSDK({
resource: resourceFromAttributes({
"service.name": OTEL_SERVICE_NAME,
"service.version": OTEL_SERVICE_VERSION,
"deployment.environment.name": "development",
}),
traceExporter: new OTLPTraceExporter({
url: `${OTEL_BASE_URL}/v1/traces`,
}),
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (5)
Previously missed (1) — in code that hasn't changed since the last review.
apps/api/src/telemetry/config.ts:3
- Telemetry config is hard-coded (service name/version and collector base URL). This will likely be wrong in production deployments and makes it hard to configure via environment variables.
export const OTEL_SERVICE_NAME = "events.compsoc.api";
export const OTEL_SERVICE_VERSION = "1.0.0";
export const OTEL_BASE_URL = "http://127.0.0.1:4318";
apps/api/src/modules/webhooks/clerk.ts:29
- Typo in comment: “recieved” should be “received”.
// User data, which recieved from clerk on registration
apps/api/src/modules/webhooks/clerk.ts:92
request.rawBodyis asserted non-null when verifying the Svix signature. If the request doesn’t hit the custom JSON parser (e.g. different/missing Content-Type), this will throw and be reported as an “Invalid webhook signature”, which is misleading.
const wh = new Webhook(webhookSecret);
let event: ClerkWebhookEvent;
try {
event = wh.verify(request.rawBody!, {
"svix-id": svixId,
apps/api/src/telemetry/sdk.ts:37
sdk.start()is async but is not awaited. That can race app startup (instrumentations may not be fully registered before the server loads) and any startup failure becomes an unhandled rejection.
sdk.start();
apps/api/src/telemetry/sdk.ts:13
deployment.environment.nameis hard-coded to "development". This will mis-label spans in non-dev environments; it should come from configuration (e.g. NODE_ENV / OTEL_DEPLOYMENT_ENVIRONMENT).
import { OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION, OTEL_BASE_URL } from "./config.js";
const sdk = new NodeSDK({
resource: resourceFromAttributes({
"service.name": OTEL_SERVICE_NAME,
"service.version": OTEL_SERVICE_VERSION,
"deployment.environment.name": "development",
}),
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
apps/api/src/telemetry/config.ts:3
- These telemetry constants are hard-coded to development defaults (service version and OTLP endpoint). This will silently point production deployments at localhost unless overridden in code; prefer reading from environment variables with sensible fallbacks.
export const OTEL_SERVICE_NAME = "events.compsoc.api";
export const OTEL_SERVICE_VERSION = "1.0.0";
export const OTEL_BASE_URL = "http://127.0.0.1:4318";
apps/api/src/telemetry/sdk.ts:37
NodeSDK.start()should be awaited and paired with a shutdown handler so traces flush on SIGTERM/SIGINT (especially in Docker). As written, startup errors can be missed and spans may be dropped on process exit.
sdk.start();
apps/api/src/modules/webhooks/clerk.ts:29
- Typo in comment: "recieved" → "received".
// User data, which recieved from clerk on registration
| @@ -0,0 +1,47 @@ | |||
| import { Attributes, Span, SpanStatusCode, trace } from "@opentelemetry/api"; | |||
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 17 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
apps/api/src/modules/registration/service.ts:182
- The
compsoc.registration.event_capacityspan attribute is sometimes a number (elsewhere) and sometimes the string "unlimited". Keeping a consistent attribute type avoids downstream query/aggregation issues in OTel backends.
span.setAttributes({
"compsoc.registration.active_count": activeCount,
"compsoc.registration.event_capacity": event.capacity ?? "unlimited",
});
apps/api/src/modules/registration/service.ts:247
- This route returns
updatedCountas the requested count (data.userIds.length) even when fewer (or zero) rows were actually updated. SinceupdateStatusBatch()returns the updated rows, the response should reflectupdated.lengthfor accuracy.
span.setAttribute("compsoc.registration.updated_count", updated.length);
setOutcome(updated.length === 0 ? "no_matches" : "updated");
return { updatedCount: data.userIds.length };
apps/api/src/modules/webhooks/clerk.ts:30
- Spelling: "recieved" -> "received".
// User data, which recieved from clerk on registration
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 18 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
apps/api/src/modules/registration/service.ts:247
batchUpdateStatuscomputes the number of updated rows (updated.length) but returnsupdatedCountas the requested count (data.userIds.length). This can mislead API consumers and log/trace data when some userIds don't match registrations.
span.setAttribute("compsoc.registration.updated_count", updated.length);
setOutcome(updated.length === 0 ? "no_matches" : "updated");
return { updatedCount: data.userIds.length };
apps/api/src/modules/webhooks/clerk.ts:30
- Typo in comment: "recieved" should be "received".
// User data, which recieved from clerk on registration
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (3)
apps/api/src/modules/webhooks/clerk.ts:30
- The comment has a spelling mistake: "recieved" → "received".
apps/api/src/bootstrap.ts:7 NodeSDK.start()is async in the OpenTelemetry Node SDK; calling it withoutawaitcan lead to instrumentation not being fully initialised before the app starts, and startup failures may become unhandled rejections.
const { sdk } = await import("./telemetry/sdk.js");
sdk.start();
await import("./app.js");
apps/api/src/modules/registration/service.ts:247
batchUpdateStatusreturnsupdatedCountas the requested number of userIds, butregistrationStore.updateStatusBatch()returns the actual updated rows. This can report incorrect counts when some userIds don't match registrations.
return { updatedCount: data.userIds.length };
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 18 changed files in this pull request and generated 1 comment.
Suppressed comments (5)
apps/api/src/modules/webhooks/clerk.ts:31
- Spelling in comment: "recieved" → "received".
// User data, which recieved from clerk on registration
apps/api/src/bootstrap.ts:7
NodeSDK.start()is async; calling it without awaiting means the server can start handling requests before instrumentation/exporters are ready, and startup errors may be silently dropped. Await the SDK start before importing/starting the app.
const { sdk } = await import("./telemetry/sdk.js");
sdk.start();
await import("./app.js");
apps/api/src/modules/registration/service.ts:178
spotsLeftuses a truthy check onevent.capacity; if capacity can be0, this incorrectly treats it as unlimited. Use an explicitnullcheck (matching the other capacity logic in this file).
const spotsLeft = event.capacity ? event.capacity - activeCount : Infinity;
apps/api/src/modules/registration/service.ts:248
batchUpdateStatusnow computesupdated.lengthand uses it for telemetry/outcome, but the API response still reportsupdatedCountas the requested count, which can be inaccurate when some userIds don’t match any registration. Return the actual updated count.
span.setAttribute("compsoc.registration.updated_count", updated.length);
setOutcome(updated.length === 0 ? "no_matches" : "updated");
return { updatedCount: data.userIds.length };
});
apps/api/drizzle.config.ts:11
@ts-expect-errorwill turn into a hard compile error ifprocessbecomes typed (unused expect-error). If the goal is just to silence missing Node globals in drizzle-kit’s config compilation,@ts-ignoreis safer, or importnode:processexplicitly.
dbCredentials: {
// @ts-expect-error, it doesn't see types for the process
url: process.env.DATABASE_URL!,
},
| import { SpanStatusCode, trace, type Span } from "@opentelemetry/api"; | ||
| import { env } from "../../env.js"; | ||
| import z from "zod"; |
No description provided.