Skip to content

Add multi-instance SDK support via delegating providers#177

Open
JacksonWeber wants to merge 2 commits into
microsoft:mainfrom
JacksonWeber:feature/multi-instance-sdk
Open

Add multi-instance SDK support via delegating providers#177
JacksonWeber wants to merge 2 commits into
microsoft:mainfrom
JacksonWeber:feature/multi-instance-sdk

Conversation

@JacksonWeber

@JacksonWeber JacksonWeber commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds opt-in support for running multiple isolated Microsoft OpenTelemetry SDK instances in a single Node.js runtime. Today useMicrosoftOpenTelemetry() registers global providers per signal, so a second initialization clobbers the first. This PR introduces a parent/child delegating-provider architecture so independent pipelines (e.g. two Application Insights resources with different connection strings) can coexist.

GA constraint respected: all changes are additive and non-breaking. The existing single-instance useMicrosoftOpenTelemetry() path is untouched.

What's new

  • createMicrosoftOpenTelemetryInstance(options, { makeDefault? }) returns a MicrosoftOpenTelemetryInstance handle (getTracer/getMeter/getLogger, runWithInstance, forceFlush, shutdown).
  • runWithMicrosoftOpenTelemetryInstance(id, fn) binds an ambient current instance so code using the global OTel API routes to the right pipeline.

Design

src/distro/multiInstance/:

  • instanceRegistry.ts — registry + default instance + AsyncLocalStorage-backed ambient current instance.
  • delegatingProviders.ts — Parent Tracer/Meter/Logger providers that resolve the current child per call (never cached) and delegate, with Noop fallbacks.
  • globalSetup.ts — registers the parent providers + a single shared context manager + composite propagator exactly once.
  • instance.ts — builds each child as standalone NodeTracerProvider/MeterProvider/LoggerProvider (no NodeSDK.start(), no global registration), wiring Azure Monitor handlers + caller processors + console fallback, then registers it.

Routing model: parents resolve a single current instance (ambient via runWithInstance → default fallback) and delegate, so telemetry stays isolated per instance rather than fanned out.

Testing

  • New test/internal/functional/multiInstance.test.ts: two instances with distinct (fake) connection strings; asserts each instance's spans reach only its own exporter, and that global-API spans inside runWithInstance route to the bound instance.
  • Verified live against two real Application Insights resources via a standalone sample app (all breeze /v2.1/track requests returned HTTP 200).
  • Full unit + functional suites green; ESM + CJS build, lint, and format pass.

Dependencies

  • Promotes @opentelemetry/context-async-hooks from transitive to a direct dependency (now imported directly to register the context manager, since the multi-instance path does not call NodeSDK.start()). No overrides used.

Introduce createMicrosoftOpenTelemetryInstance to run multiple isolated SDK
instances in one Node.js runtime. Parent (delegating) Tracer/Meter/Logger
providers route per-call to the current child instance, resolved via an
AsyncLocalStorage-backed ambient context (runWithInstance) with a default
fallback. Each instance owns its own resource, sampler, processors, readers,
and exporters. Additive and opt-in; the existing useMicrosoftOpenTelemetry
single-instance path is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 17, 2026 21:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in multi-instance Microsoft OpenTelemetry SDK mode that allows multiple isolated telemetry pipelines to coexist in a single Node.js process by registering global delegating providers that route per-call to an “ambient” (AsyncLocalStorage-bound) instance, falling back to a default instance.

Changes:

  • Introduces createMicrosoftOpenTelemetryInstance() / runWithMicrosoftOpenTelemetryInstance() and the MicrosoftOpenTelemetryInstance handle type.
  • Implements multi-instance runtime: instance registry + id binding, one-time global setup (context manager/propagator + parent providers), and delegating tracer/meter/logger providers.
  • Adds functional coverage for isolation and ambient routing; promotes @opentelemetry/context-async-hooks to a direct dependency.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/internal/functional/multiInstance.test.ts New functional tests validating per-instance isolation and ambient routing via global API
src/types.ts Adds MicrosoftOpenTelemetryInstance public interface type
src/index.ts Re-exports new multi-instance APIs/types from the package entrypoint
src/distro/multiInstance/instanceRegistry.ts Registry + AsyncLocalStorage-backed “current instance” binding and resolution
src/distro/multiInstance/instance.ts Builds per-instance child providers/pipelines and lifecycle methods (flush/shutdown)
src/distro/multiInstance/index.ts Exposes multi-instance APIs from the distro layer
src/distro/multiInstance/globalSetup.ts One-time process-global setup for context manager/propagator + parent providers
src/distro/multiInstance/delegatingProviders.ts Parent providers + delegating tracer/meter/logger implementations
src/distro/index.ts Surfaces multi-instance exports through src/distro
package.json Adds @opentelemetry/context-async-hooks direct dependency
package-lock.json Locks the new dependency for npm ci installs

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/distro/multiInstance/instanceRegistry.ts
Comment thread src/distro/multiInstance/instance.ts Outdated
Comment thread test/internal/functional/multiInstance.test.ts
Comment thread test/internal/functional/multiInstance.test.ts
- withInstance: skip binding unknown/stale ids so resolution falls back to the
  default instance instead of producing silent no-op telemetry.
- instance.shutdown: wrap disposers via Promise.resolve().then so a synchronous
  throw is captured and does not abort the rest of shutdown.
- multiInstance test: also disable the logs provider and the global context
  manager in afterEach to prevent cross-test contamination.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@rads-1996 rads-1996 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to break the PR into small chunks, so it is easier to review? Or does it make logical sense to have everything together?

@JacksonWeber

JacksonWeber commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Is it possible to break the PR into small chunks, so it is easier to review? Or does it make logical sense to have everything together?

I don't believe that will work well for this PR as it implements a single contained feature. If a single component of it is split off it won't function. Only way I can think to split it is per signal, but I'm not sure that'd make it much easier to review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants