Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [Unreleased]

### Features Added
- Instrument outgoing `fetch` (undici) requests so HTTP client spans are captured for `fetch`-based clients such as the OpenAI SDK used by LangChain

### Other Changes
- Remove the unused `AZURE_MONITOR_DISTRO_VERSION` env var and its constant; the distro reports its version via `MICROSOFT_OPENTELEMETRY_VERSION`

Expand Down
18 changes: 18 additions & 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 @@ -98,6 +98,7 @@
"@opentelemetry/instrumentation-mysql": "^0.64.0",
"@opentelemetry/instrumentation-pg": "^0.70.0",
"@opentelemetry/instrumentation-redis": "^0.66.0",
"@opentelemetry/instrumentation-undici": "^0.29.0",
"@opentelemetry/instrumentation-winston": "^0.62.0",
"@opentelemetry/resource-detector-azure": "^0.26.0",
"@opentelemetry/resources": "^2.8.0",
Expand Down
34 changes: 34 additions & 0 deletions src/distro/distro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from "../azureMonitor/index.js";
import { isOtlpEnabled, createOtlpComponents } from "../otlp/index.js";
import { A365Configuration, Agent365Exporter, A365SpanProcessor } from "../a365/index.js";
import { resolveAgent365Endpoint } from "../a365/exporter/utils.js";
import { configureA365Logger } from "../a365/logging.js";
import {
GenAIMainAgentLogRecordProcessor,
Expand Down Expand Up @@ -62,6 +63,7 @@ let isShutdown = false;

const A365_DISABLED_INSTRUMENTATIONS_BY_DEFAULT: ReadonlyArray<keyof InstrumentationOptions> = [
"http",
"undici",
"azureSdk",
"mongoDb",
"mySql",
Expand All @@ -79,6 +81,33 @@ const A365_DISABLED_INSTRUMENTATIONS_BY_DEFAULT: ReadonlyArray<keyof Instrumenta
*/
const REDIS_LINKED_KEYS: ReadonlyArray<keyof InstrumentationOptions> = ["redis", "redis4"];

/**
* Resolve the origin(s) of the A365 fetch-based exporter endpoint so the undici
* instrumentation can skip tracing our own telemetry export requests. Returns
* an empty list when the A365 exporter is not active or the endpoint cannot be
* resolved.
*
* @internal
*/
export function _resolveA365ExporterOrigins(a365Config: A365Configuration): string[] {

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.

A little confused here, why do we need to do this? I think A365 drops the http spans anyways.

if (!a365Config.enabled || !a365Config.enableObservabilityExporter) {
return [];
}
let baseUrl = a365Config.domainOverride;
if (!baseUrl) {
try {
baseUrl = resolveAgent365Endpoint(a365Config.clusterCategory);
} catch {
return [];
}
}
try {
return [new URL(baseUrl).origin];
} catch {
return [];
}
}

/**
* When A365 export is enabled, default to GenAI-focused telemetry by disabling
* non-GenAI instrumentations unless callers explicitly configure them.
Expand Down Expand Up @@ -241,8 +270,13 @@ export function useMicrosoftOpenTelemetry(options?: MicrosoftOpenTelemetryOption
);

// ── Instrumentations, sampler, and views (always created) ─────────
// When the fetch-based A365 exporter is active, tell the undici
// instrumentation to skip its export requests so they are not traced as
// spurious client (dependency) spans on other exporters (e.g. Azure Monitor).
const ignoreUndiciOrigins = _resolveA365ExporterOrigins(a365Config);
const instrumentations = createInstrumentations(config, {
filterAzureMonitorRequests: azureMonitorEnabled,
ignoreUndiciOrigins,
});
const sampler = createSampler(config);
const views: ViewOptions[] = createViews(config);
Expand Down
34 changes: 33 additions & 1 deletion src/distro/instrumentations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import type {
IgnoreOutgoingRequestFunction,
} from "@opentelemetry/instrumentation-http";
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import type {
UndiciInstrumentationConfig,
UndiciRequest,
} from "@opentelemetry/instrumentation-undici";
import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
import { MongoDBInstrumentation } from "@opentelemetry/instrumentation-mongodb";
import { MySQLInstrumentation } from "@opentelemetry/instrumentation-mysql";
import { PgInstrumentation } from "@opentelemetry/instrumentation-pg";
Expand Down Expand Up @@ -43,10 +48,11 @@ import { logLevelToSeverityNumber } from "../azureMonitor/utils/logUtils.js";
*/
export function createInstrumentations(
config: InternalConfig,
options?: { filterAzureMonitorRequests?: boolean },
options?: { filterAzureMonitorRequests?: boolean; ignoreUndiciOrigins?: string[] },
): Instrumentation[] {
const instrumentations: Instrumentation[] = [];
const filterAzureMonitor = options?.filterAzureMonitorRequests ?? false;
const ignoreUndiciOrigins = options?.ignoreUndiciOrigins ?? [];

// ── Trace instrumentations ──────────────────────────────────────
if (config.instrumentationOptions.http?.enabled) {
Expand All @@ -67,6 +73,32 @@ export function createInstrumentations(
instrumentations.push(new HttpInstrumentation(httpConfig));
}

// Undici / global `fetch` — instruments outgoing requests the Node
// http/https instrumentation does not see (e.g. the OpenAI SDK's fetch calls).
if (config.instrumentationOptions.undici?.enabled) {
let undiciConfig = config.instrumentationOptions.undici as UndiciInstrumentationConfig;

// Undici does not honour tracing suppression, so requests made by our own
// fetch-based telemetry exporters (e.g. the A365 exporter) would otherwise
// be traced as spurious client spans. Skip requests to those origins while
// still delegating to any caller-provided ignore hook. Clone the config so
// the caller-owned object is never mutated.
if (ignoreUndiciOrigins.length > 0) {
const providedIgnoreRequestHook = undiciConfig.ignoreRequestHook;
undiciConfig = {
...undiciConfig,
ignoreRequestHook: (request: UndiciRequest) => {
if (ignoreUndiciOrigins.includes(request.origin)) {
return true;
}
return providedIgnoreRequestHook ? providedIgnoreRequestHook(request) : false;
},
};
}

instrumentations.push(new UndiciInstrumentation(undiciConfig));
}

if (config.instrumentationOptions.azureSdk?.enabled) {
instrumentations.push(createAzureSdkInstrumentation(config.instrumentationOptions.azureSdk));
}
Expand Down
3 changes: 2 additions & 1 deletion src/shared/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class InternalConfig {
/** Azure Monitor Exporter Configuration */
public azureMonitorExporterOptions: AzureMonitorExporterOptions;
/**
* OpenTelemetry Instrumentations configuration included as part of Azure Monitor (azureSdk, http, mongoDb, mySql, postgreSql, redis, redis4)
* OpenTelemetry Instrumentations configuration included as part of Azure Monitor (azureSdk, http, undici, mongoDb, mySql, postgreSql, redis, redis4)
*/
public instrumentationOptions: InstrumentationOptions;
/** Enable Live Metrics feature */
Expand Down Expand Up @@ -82,6 +82,7 @@ export class InternalConfig {
this.metricExportIntervalMillis = this.calculateMetricExportInterval();
this.instrumentationOptions = {
http: { enabled: true },
undici: { enabled: true },
azureSdk: { enabled: true },
mongoDb: { enabled: true },
mySql: { enabled: true },
Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.
import type { AzureMonitorExporterOptions } from "@azure/monitor-opentelemetry-exporter";
import type { InstrumentationConfig } from "@opentelemetry/instrumentation";
import type { UndiciInstrumentationConfig } from "@opentelemetry/instrumentation-undici";
import type { Resource } from "@opentelemetry/resources";
import type { LogRecordProcessor } from "@opentelemetry/sdk-logs";
import type { MetricReader, ViewOptions } from "@opentelemetry/sdk-metrics";
Expand Down Expand Up @@ -84,6 +85,13 @@ export interface InstrumentationOptions {
azureSdk?: InstrumentationConfig;
/** HTTP Instrumentation Config */
http?: InstrumentationConfig;
/**
* Undici / global `fetch` Instrumentation Config.
* Instruments outgoing requests made via the global `fetch` API (undici),
* which the Node `http`/`https` instrumentation does not cover. Required for
* HTTP client spans from `fetch`-based clients such as the OpenAI SDK.
*/
undici?: UndiciInstrumentationConfig;
/** MongoDB Instrumentation Config */
mongoDb?: InstrumentationConfig;
/** MySQL Instrumentation Config */
Expand Down
Loading
Loading