Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"@microsoft/applicationinsights-web-snippet": "^1.2.3",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.218.0",
"@opentelemetry/context-async-hooks": "^2.7.1",
"@opentelemetry/core": "^2.7.1",
"@opentelemetry/exporter-logs-otlp-http": "^0.218.0",
"@opentelemetry/exporter-metrics-otlp-http": "^0.218.0",
Expand Down
5 changes: 5 additions & 0 deletions src/distro/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ export type {
BrowserSdkLoaderOptions,
A365Options,
} from "./types.js";
export type { MicrosoftOpenTelemetryInstance } from "../types.js";
export { MICROSOFT_OPENTELEMETRY_VERSION } from "./types.js";

export { useMicrosoftOpenTelemetry, shutdownMicrosoftOpenTelemetry } from "./distro.js";
export {
createMicrosoftOpenTelemetryInstance,
runWithMicrosoftOpenTelemetryInstance,
} from "./multiInstance/index.js";
167 changes: 167 additions & 0 deletions src/distro/multiInstance/delegatingProviders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import type {
Tracer,
TracerProvider,
Span,
SpanOptions,
Context,
Meter,
MeterProvider,
MeterOptions,
MetricOptions,
BatchObservableCallback,
Observable,
Counter,
UpDownCounter,
Gauge,
Histogram,
ObservableGauge,
ObservableCounter,
ObservableUpDownCounter,
} from "@opentelemetry/api";
import { createNoopMeter, ProxyTracerProvider } from "@opentelemetry/api";
import type { Logger, LoggerProvider, LoggerOptions, LogRecord } from "@opentelemetry/api-logs";
import { NOOP_LOGGER } from "@opentelemetry/api-logs";

import { resolveInstanceProviders } from "./instanceRegistry.js";

// Shared fallbacks used when no instance is registered/resolved yet. They are
// no-ops so that early or out-of-band global API access never throws.
const NOOP_TRACER_PROVIDER = new ProxyTracerProvider();
const NOOP_METER = createNoopMeter();

/**
* A Tracer that resolves the current instance's tracer on every call. Resolution
* MUST be per-call (never cached) because the ambient instance changes with the
* active context.
*/
class DelegatingTracer implements Tracer {
constructor(
private readonly name: string,
private readonly version?: string,
private readonly options?: { schemaUrl?: string },
) {}

private delegate(): Tracer {
const providers = resolveInstanceProviders();
const provider: TracerProvider = providers?.tracerProvider ?? NOOP_TRACER_PROVIDER;
return provider.getTracer(this.name, this.version, this.options);
}

startSpan(name: string, options?: SpanOptions, context?: Context): Span {
return this.delegate().startSpan(name, options, context);
}

// The api defines several overloads for startActiveSpan; forward all args.
startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F): ReturnType<F>;
startActiveSpan<F extends (span: Span) => unknown>(
name: string,
options: SpanOptions,
fn: F,
): ReturnType<F>;
startActiveSpan<F extends (span: Span) => unknown>(
name: string,
options: SpanOptions,
context: Context,
fn: F,
): ReturnType<F>;
startActiveSpan(name: string, ...args: unknown[]): unknown {
return (this.delegate().startActiveSpan as (...a: unknown[]) => unknown)(name, ...args);
}
}

/**
* Global parent TracerProvider registered once. It owns no pipeline itself; it
* delegates to the resolved child instance's TracerProvider.
*/
export class ParentTracerProvider implements TracerProvider {
getTracer(name: string, version?: string, options?: { schemaUrl?: string }): Tracer {
return new DelegatingTracer(name, version, options);
}
}

/** A Meter that resolves the current instance's meter on every instrument call. */
class DelegatingMeter implements Meter {
constructor(
private readonly name: string,
private readonly version?: string,
private readonly options?: MeterOptions,
) {}

private delegate(): Meter {
const providers = resolveInstanceProviders();
const provider: MeterProvider | undefined = providers?.meterProvider;
return provider ? provider.getMeter(this.name, this.version, this.options) : NOOP_METER;
}

createGauge(name: string, options?: MetricOptions): Gauge {
return this.delegate().createGauge(name, options);
}
createHistogram(name: string, options?: MetricOptions): Histogram {
return this.delegate().createHistogram(name, options);
}
createCounter(name: string, options?: MetricOptions): Counter {
return this.delegate().createCounter(name, options);
}
createUpDownCounter(name: string, options?: MetricOptions): UpDownCounter {
return this.delegate().createUpDownCounter(name, options);
}
createObservableGauge(name: string, options?: MetricOptions): ObservableGauge {
return this.delegate().createObservableGauge(name, options);
}
createObservableCounter(name: string, options?: MetricOptions): ObservableCounter {
return this.delegate().createObservableCounter(name, options);
}
createObservableUpDownCounter(name: string, options?: MetricOptions): ObservableUpDownCounter {
return this.delegate().createObservableUpDownCounter(name, options);
}
addBatchObservableCallback(callback: BatchObservableCallback, observables: Observable[]): void {
this.delegate().addBatchObservableCallback(callback, observables);
}
removeBatchObservableCallback(
callback: BatchObservableCallback,
observables: Observable[],
): void {
this.delegate().removeBatchObservableCallback(callback, observables);
}
Comment thread
JacksonWeber marked this conversation as resolved.
Outdated
}

/** Global parent MeterProvider registered once; delegates to the resolved child. */
export class ParentMeterProvider implements MeterProvider {
getMeter(name: string, version?: string, options?: MeterOptions): Meter {
return new DelegatingMeter(name, version, options);
}
}

/** A Logger that resolves the current instance's logger on every emit. */
class DelegatingLogger implements Logger {
constructor(
private readonly name: string,
private readonly version?: string,
private readonly options?: LoggerOptions,
) {}

private delegate(): Logger {
const providers = resolveInstanceProviders();
return providers
? providers.loggerProvider.getLogger(this.name, this.version, this.options)
: NOOP_LOGGER;
}

emit(logRecord: LogRecord): void {
this.delegate().emit(logRecord);
}

enabled(options?: Parameters<Logger["enabled"]>[0]): boolean {
return this.delegate().enabled(options);
}
}

/** Global parent LoggerProvider registered once; delegates to the resolved child. */
export class ParentLoggerProvider implements LoggerProvider {
getLogger(name: string, version?: string, options?: LoggerOptions): Logger {
return new DelegatingLogger(name, version, options);
}
}
63 changes: 63 additions & 0 deletions src/distro/multiInstance/globalSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { context, metrics, propagation, trace } from "@opentelemetry/api";
import { logs } from "@opentelemetry/api-logs";
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
import {
CompositePropagator,
W3CBaggagePropagator,
W3CTraceContextPropagator,
} from "@opentelemetry/core";

import {
ParentLoggerProvider,
ParentMeterProvider,
ParentTracerProvider,
} from "./delegatingProviders.js";

let globalSetupDone = false;

/**
* Register the parent (delegating) providers and the shared process-global
* context manager + propagator exactly once. Context and propagation are
* process-wide concerns shared by every instance, so they are NOT duplicated
* per instance.
*
* Idempotent: safe to call on every `useMicrosoftOpenTelemetry()` /
* `createMicrosoftOpenTelemetryInstance()` invocation.
*/
export function ensureGlobalSetup(): void {
if (globalSetupDone) {
return;
}

// Clear any stale OpenTelemetry API global state to avoid version conflicts
// (mirrors the cleanup performed by the single-instance distro path).
trace.disable();
metrics.disable();
logs.disable();
const globalOpentelemetryApiKey = Symbol.for("opentelemetry.js.api.1");
delete (globalThis as Record<symbol, unknown>)[globalOpentelemetryApiKey];

const contextManager = new AsyncLocalStorageContextManager();
contextManager.enable();
context.setGlobalContextManager(contextManager);

propagation.setGlobalPropagator(
new CompositePropagator({
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
}),
);

trace.setGlobalTracerProvider(new ParentTracerProvider());
metrics.setGlobalMeterProvider(new ParentMeterProvider());
logs.setGlobalLoggerProvider(new ParentLoggerProvider());

globalSetupDone = true;
}

/** Test helper: allow re-running global setup. @internal */
export function _resetGlobalSetup(): void {
globalSetupDone = false;
}
8 changes: 8 additions & 0 deletions src/distro/multiInstance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

export { createMicrosoftOpenTelemetryInstance } from "./instance.js";
export {
withInstance as runWithMicrosoftOpenTelemetryInstance,
getCurrentInstanceId as _getCurrentInstanceId,
} from "./instanceRegistry.js";
Loading
Loading