Medplum v5 OTEL
# support
n
Hi self-hosters, we recently updated our instance to v5.0.15 from 4.x and seemingly lost much of our tracing. We've done some CI modifications in our fork, so I'm hoping if somebody can validate that they're still getting
@opentelemetry/instrumentation-net
,
@opentelemetry/instrumentation-express
,
@opentelemetry/instrumentation-ioredis
spans in their traces after the v5 / ESM everywhere upgrade. We only seem to be getting
@opentelemetry/instrumentation-pg
post-upgrade. I'm always surprised otel-js and ESM is still [weird](https://github.com/open-telemetry/opentelemetry-js/issues/4933) in 2026.
i
@nathan-watkins-unityai , I just tested this on an Azure k8s cluster running 5.0.12 and I was able to see @opentelemetry/instrumentation-net, @opentelemetry/instrumentation-express, @opentelemetry/instrumentation-ioredis spans in my traces. I wonder if there is something related to your fork that has broken it.
n
Thank you @Ian Plunkett ! Would you mind clarifying what the entrypoint is? The [Dockerfile](https://github.com/medplum/medplum/blob/main/Dockerfile#L40) seems to use
--require
while the [start](https://github.com/medplum/medplum/blob/main/packages/server/package.json#L25) command uses the
--import
flag. Do you all use the [--experimental-loader](https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/esm-support.md#instrumentation-hook-required-for-esm) ?
i
My k8s cluster is using the Dockerfile entrypoint.
node --require ./packages/server/dist/otel/instrumentation.js packages/server/dist/index.js
n
Revisiting this- I ended up resolving it through some combination of - Updating my base image to Node 24 (from 22.22) - Updating medplum from 5.0.15 -> 5.1.8 - Adding
--experimental-loader=@opentelemetry/instrumentation/hook.mjs
to the startup command, per otel-js docs. I've confirmed it doesn't work for me without this. On Node 22, I was getting exit code 0 with the loader inlined in the startup command, so I think the loader was getting treated as the main module, so [if (import.meta.main)](https://github.com/medplum/medplum/blob/49a06ab18c252a1f93d049003f0e13da2f461c47/packages/server/src/index.ts#L58) wasn't truthy. FWIW- Claude debugging was also locked in on a transitive dependency issue that existed in 5.0.15 but not in 5.1.8. Description below but I couldn't validate if that specifically was problematic. Hopfully one day soon OTel and ESM won't be a trainwreck.
Copy code
@appsignal/opentelemetry-instrumentation-bullmq v0.7.3 depends on @opentelemetry/instrumentation ^0.52.1. Every other OTel package needs ^0.211.0+. Since pre-1.0 semver treats minor bumps as breaking, npm can't deduplicate -- it installs v0.52.1 at the root and nests v0.211+ under each package. Each nested copy brings its own import-in-the-middle, and each copy has an isolated hook registry. The ESM loader hook talks to one registry; the instrumentation hooks register in others. They never connect, so ESM imports of CJS packages like express and ioredis are never intercepted.
Upstream Medplum fixed this by updating to v0.8.0, which depends on ^0.213.0 -- compatible with everything else, so npm deduplicates to a single import-in-the-middle instance with one shared registry.
28 Views