fix(server): deduplicate @opentelemetry/api to fix NoopMeterProvider (#20231)
## Summary **All OTel metrics in twenty-server have been silently dropped since April 30.** ### Root cause PR #20149 (`bump @sentry/profiling-node 10.27→10.51`) pulled in `@sentry/node@10.51.0`, which declares `@opentelemetry/api: ^1.9.1` as a **dependency** (not peer). Yarn installed it as a **nested** copy at `1.9.1`, while the hoisted copy stayed at `1.9.0`. At startup in `instrument.ts`: 1. `Sentry.init()` uses the **nested `1.9.1`** to register `trace`, `propagation`, `context` on the OTel global → global version becomes **`1.9.1`** 2. `setGlobalMeterProvider()` uses the **hoisted `1.9.0`** → `registerGlobal` sees version mismatch (`1.9.1` ≠ `1.9.0`) → **silently returns `false`** 3. Global stays `NoopMeterProvider` → every counter, gauge, and histogram in the server is a no-op ### What this PR does 1. **Reverts three troubleshooting PRs** that are no longer needed now that the root cause is identified: - #20230 — heartbeat gauge - #20228 — OTLP export lifecycle logs - #20221 — Sentry revert to 10.27 (which never actually downgraded in `yarn.lock` since `^10.27.0` resolved to `10.51.0`) 2. **Fixes the root cause**: - Root Yarn resolution pinning `@opentelemetry/api` to `1.9.1` → single copy in the entire tree, Sentry and Twenty share the same instance - Named import in `instrument.ts` (`import { metrics as otelMetrics }` instead of default import) as defense-in-depth against CJS interop issues ### Verified on dev cluster Exec'd into the running pod and confirmed: - `@sentry/node` nests `@opentelemetry/api@1.9.1`, hoisted is `1.9.0` - `Sentry.init()` → global version `1.9.1` → `setGlobalMeterProvider` with VERSION `1.9.0` → returns `false` → `NoopMeterProvider` - Same-version registration returns `true` → `MeterProvider` ✓ ## Test plan - [ ] CI passes (lint, typecheck, build) - [ ] Deploy to dev cluster and verify metrics flow to collector - [ ] Confirm `node_modules/@opentelemetry/api/package.json` shows `1.9.1` with no nested copy under `@sentry/` --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+22
-17
@@ -4,6 +4,7 @@ import {
|
||||
type OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
type JobsOptions,
|
||||
MetricsTime,
|
||||
@@ -28,6 +29,7 @@ import { type MessageQueue } from 'src/engine/core-modules/message-queue/message
|
||||
import { getJobKey } from 'src/engine/core-modules/message-queue/utils/get-job-key.util';
|
||||
import { type MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
||||
import { applyWorkspaceSentryContextFromJobData } from 'src/engine/core-modules/sentry/utils/apply-workspace-sentry-context-from-job-data.util';
|
||||
|
||||
export type BullMQDriverOptions = QueueOptions;
|
||||
|
||||
@@ -108,25 +110,28 @@ export class BullMQDriver
|
||||
|
||||
this.workerMap[queueName] = new Worker(
|
||||
queueName,
|
||||
async (job) => {
|
||||
// TODO: Correctly support for job.id
|
||||
const timeStart = performance.now();
|
||||
const workspaceId = job.data?.workspaceId;
|
||||
const workspaceSuffix = workspaceId
|
||||
? ` [workspace=${workspaceId}]`
|
||||
: '';
|
||||
async (job) =>
|
||||
Sentry.withIsolationScope(async () => {
|
||||
applyWorkspaceSentryContextFromJobData(job.data);
|
||||
|
||||
this.logger.log(
|
||||
`Processing job ${job.id} with name ${job.name} on queue ${queueName}${workspaceSuffix}`,
|
||||
);
|
||||
await handler({ data: job.data, id: job.id ?? '', name: job.name });
|
||||
const timeEnd = performance.now();
|
||||
const executionTime = timeEnd - timeStart;
|
||||
// TODO: Correctly support for job.id
|
||||
const timeStart = performance.now();
|
||||
const workspaceId = job.data?.workspaceId;
|
||||
const workspaceSuffix = workspaceId
|
||||
? ` [workspace=${workspaceId}]`
|
||||
: '';
|
||||
|
||||
this.logger.log(
|
||||
`Job ${job.id} with name ${job.name} processed on queue ${queueName} in ${executionTime.toFixed(2)}ms${workspaceSuffix}`,
|
||||
);
|
||||
},
|
||||
this.logger.log(
|
||||
`Processing job ${job.id} with name ${job.name} on queue ${queueName}${workspaceSuffix}`,
|
||||
);
|
||||
await handler({ data: job.data, id: job.id ?? '', name: job.name });
|
||||
const timeEnd = performance.now();
|
||||
const executionTime = timeEnd - timeStart;
|
||||
|
||||
this.logger.log(
|
||||
`Job ${job.id} with name ${job.name} processed on queue ${queueName} in ${executionTime.toFixed(2)}ms${workspaceSuffix}`,
|
||||
);
|
||||
}),
|
||||
workerOptions,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user