Skip to content

Commit 0569915

Browse files
authored
Add OpenTelemetry Global Detection to the Agent (#1483)
* Add the detection of optnelemetry global providers to the agent. * Update agent loader tests.
1 parent 9c2dfcf commit 0569915

3 files changed

Lines changed: 140 additions & 0 deletions

File tree

src/agent/agentLoader.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ export class AgentLoader {
120120
console.log(msg);
121121
return;
122122
}
123+
// Detect and report OpenTelemetry globals before attempting to load the agent
124+
this._detectOpenTelemetryGlobals();
123125
if (this._validate()) {
124126
try {
125127
// Set environment variable to auto attach so the distro is aware of the attach state
@@ -195,6 +197,73 @@ export class AgentLoader {
195197
}
196198
}
197199

200+
private _detectOpenTelemetryGlobals(): void {
201+
try {
202+
const detectedProviders: string[] = [];
203+
204+
// Check for OpenTelemetry globals directly on the global object
205+
// The OpenTelemetry API stores globals using Symbol.for('opentelemetry.js.api.<major>')
206+
// This avoids calling the API methods which could have side effects
207+
// Try v1 first, then fallback to v2 for future compatibility
208+
const otelSymbolV1 = Symbol.for('opentelemetry.js.api.1');
209+
const otelSymbolV2 = Symbol.for('opentelemetry.js.api.2');
210+
const otelGlobal = (global as any)[otelSymbolV1] || (global as any)[otelSymbolV2];
211+
212+
if (otelGlobal) {
213+
// Check for registered TracerProvider
214+
if (otelGlobal["trace"]) {
215+
const traceProviderName = otelGlobal["trace"]?.constructor?.name;
216+
// ProxyTracerProvider wraps the real provider - check the delegate
217+
if (traceProviderName === 'ProxyTracerProvider') {
218+
const delegateName = otelGlobal["trace"]?._delegate?.constructor?.name;
219+
if (delegateName && delegateName !== 'NoopTracerProvider') {
220+
detectedProviders.push('TracerProvider');
221+
}
222+
} else if (traceProviderName && traceProviderName !== 'NoopTracerProvider') {
223+
detectedProviders.push('TracerProvider');
224+
}
225+
}
226+
227+
// Check for registered MeterProvider
228+
if (otelGlobal["metrics"] && otelGlobal["metrics"]?.constructor?.name !== 'ProxyMeterProvider' && otelGlobal["metrics"].constructor.name !== 'NoopMeterProvider') {
229+
detectedProviders.push('MeterProvider');
230+
}
231+
}
232+
233+
// Check for registered LoggerProvider - uses a different symbol and stores a getter function
234+
const logsSymbol = Symbol.for('io.opentelemetry.js.api.logs');
235+
const logsGlobal = (global as any)[logsSymbol];
236+
if (typeof logsGlobal === 'function') {
237+
// logsGlobal is a getter function that takes a version number and returns the provider
238+
// Try both API compatibility versions (1 and 2) to support different @opentelemetry/api-logs versions
239+
let logsProvider = logsGlobal(1); // Try v1 first
240+
if (!logsProvider || logsProvider.constructor?.name === 'NoopLoggerProvider') {
241+
logsProvider = logsGlobal(2); // Try v2 if v1 returns NOOP
242+
}
243+
const loggerProviderName = logsProvider?.constructor?.name;
244+
if (
245+
loggerProviderName &&
246+
loggerProviderName !== 'ProxyLoggerProvider' &&
247+
loggerProviderName !== 'NoopLoggerProvider'
248+
) {
249+
detectedProviders.push('LoggerProvider');
250+
}
251+
}
252+
253+
if (detectedProviders.length > 0 && this._diagnosticLogger) {
254+
const msg = `OpenTelemetry global providers detected while using Application Insights auto-attach: ${detectedProviders.join(', ')}. `;
255+
const diagnosticLog = {
256+
message: msg,
257+
messageId: DiagnosticMessageId.openTelemetryConflict
258+
} as IDiagnosticLog;
259+
this._diagnosticLogger.logMessage(diagnosticLog);
260+
}
261+
}
262+
catch (err: any) {
263+
console.log("Error detecting OpenTelemetry globals: " + err);
264+
}
265+
}
266+
198267
private _getAuthenticationCredential(): any {
199268
let credential = undefined;
200269
// Try to add AAD Token Credential

src/agent/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,5 @@ export const DiagnosticMessageId = {
4646
"prefixFailed": "3004",
4747
"aadEnabled": "3005",
4848
"unknownError": "3006",
49+
"openTelemetryConflict": "3007",
4950
}

test/unitTests/agent/agentLoader.tests.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ import sinon from "sinon";
77
import { AgentLoader } from "../../../src/agent/agentLoader";
88
import * as azureMonitor from "@azure/monitor-opentelemetry";
99
import { DiagnosticMessageId } from "../../../src/agent/types";
10+
import { trace, metrics } from "@opentelemetry/api";
11+
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-node";
12+
import { MeterProvider } from "@opentelemetry/sdk-metrics";
13+
import { logs as otelLogs } from "@opentelemetry/api-logs";
14+
import { LoggerProvider } from "@opentelemetry/sdk-logs";
1015

1116
describe("agent/agentLoader", () => {
1217
let originalEnv: NodeJS.ProcessEnv;
1318
let sandbox: sinon.SinonSandbox;
19+
let originalOtelGlobalV1: any;
20+
let originalOtelGlobalV2: any;
21+
let originalLogsGlobal: any;
1422

1523
const defaultConfig = {
1624
azureMonitorExporterOptions: {
@@ -51,11 +59,32 @@ describe("agent/agentLoader", () => {
5159

5260
beforeEach(() => {
5361
originalEnv = process.env;
62+
const otelSymbolV1 = Symbol.for('opentelemetry.js.api.1');
63+
const otelSymbolV2 = Symbol.for('opentelemetry.js.api.2');
64+
const logsSymbol = Symbol.for('io.opentelemetry.js.api.logs');
65+
originalOtelGlobalV1 = (global as any)[otelSymbolV1];
66+
originalOtelGlobalV2 = (global as any)[otelSymbolV2];
67+
originalLogsGlobal = (global as any)[logsSymbol];
5468
});
5569

5670
afterEach(() => {
5771
process.env = originalEnv;
5872
sandbox.restore();
73+
const otelSymbolV1 = Symbol.for('opentelemetry.js.api.1');
74+
const otelSymbolV2 = Symbol.for('opentelemetry.js.api.2');
75+
const logsSymbol = Symbol.for('io.opentelemetry.js.api.logs');
76+
(global as any)[otelSymbolV1] = originalOtelGlobalV1;
77+
(global as any)[otelSymbolV2] = originalOtelGlobalV2;
78+
(global as any)[logsSymbol] = originalLogsGlobal;
79+
if (typeof trace.disable === "function") {
80+
trace.disable();
81+
}
82+
if (typeof metrics.disable === "function") {
83+
metrics.disable();
84+
}
85+
if (typeof otelLogs.disable === "function") {
86+
otelLogs.disable();
87+
}
5988
});
6089

6190
it("should initialize constructor", () => {
@@ -179,4 +208,45 @@ describe("agent/agentLoader", () => {
179208
agent["_validate"]();
180209
assert.deepEqual(statusLoggerStub.args[0][0].AgentInitializedSuccessfully, false);
181210
});
211+
212+
it("should log detected OpenTelemetry tracer and meter providers", () => {
213+
const env = {
214+
["APPLICATIONINSIGHTS_CONNECTION_STRING"]: "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333",
215+
};
216+
process.env = env;
217+
const tracerProvider = new BasicTracerProvider();
218+
trace.setGlobalTracerProvider(tracerProvider);
219+
const meterProvider = new MeterProvider();
220+
metrics.setGlobalMeterProvider(meterProvider as any);
221+
222+
const agent = new AgentLoader();
223+
const diagnosticLoggerStub = sandbox.stub(agent["_diagnosticLogger"], "logMessage");
224+
225+
(agent as any)._detectOpenTelemetryGlobals();
226+
227+
assert.ok(diagnosticLoggerStub.calledOnce);
228+
const logged = diagnosticLoggerStub.args[0][0];
229+
assert.strictEqual(logged.messageId, DiagnosticMessageId.openTelemetryConflict);
230+
assert.ok((logged.message as string).includes("TracerProvider"));
231+
assert.ok((logged.message as string).includes("MeterProvider"));
232+
});
233+
234+
it("should log detected OpenTelemetry logger provider via logs symbol getter", () => {
235+
const env = {
236+
["APPLICATIONINSIGHTS_CONNECTION_STRING"]: "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333",
237+
};
238+
process.env = env;
239+
const loggerProvider = new LoggerProvider();
240+
(otelLogs as any).setGlobalLoggerProvider(loggerProvider);
241+
242+
const agent = new AgentLoader();
243+
const diagnosticLoggerStub = sandbox.stub(agent["_diagnosticLogger"], "logMessage");
244+
245+
(agent as any)._detectOpenTelemetryGlobals();
246+
247+
assert.ok(diagnosticLoggerStub.calledOnce);
248+
const logged = diagnosticLoggerStub.args[0][0];
249+
assert.strictEqual(logged.messageId, DiagnosticMessageId.openTelemetryConflict);
250+
assert.ok((logged.message as string).includes("LoggerProvider"));
251+
});
182252
});

0 commit comments

Comments
 (0)