From 15b1c481bf957839bacf63bee903db1bd882d937 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:40:47 -0700 Subject: [PATCH 1/8] docs: expand A365 migration and configuration guidance - document console exporter enablement and behavior\n- clarify instrumentation enable/disable patterns\n- align OpenAI/LangChain instrumentation interfaces with InstrumentationConfig\n- refresh GenAI samples with explicit enabled flags --- MIGRATION_A365.md | 84 +++++++++++++++++++++++++ README.md | 72 ++++++++++++++++++++- samples/src/langchainInstrumentation.ts | 1 + samples/src/openaiInstrumentation.ts | 1 + src/types.ts | 12 ++-- 5 files changed, 161 insertions(+), 9 deletions(-) diff --git a/MIGRATION_A365.md b/MIGRATION_A365.md index 795d7913..ac850d22 100644 --- a/MIGRATION_A365.md +++ b/MIGRATION_A365.md @@ -119,6 +119,15 @@ useMicrosoftOpenTelemetry({ tokenResolver: async (agentId, tenantId) => getToken(agentId, tenantId), clusterCategory: "prod", perRequestExport: true, + domainOverride: "custom.a365.example.com", // optional + authScopes: ["https://api.powerplatform.com/.default"], // optional + baggage: { + propagationEnabled: true, + enrichSpans: true, + }, + hosting: { + enabled: true, // requires @microsoft/agents-hosting + }, }, // Optional: also send to Azure Monitor azureMonitor: { @@ -129,6 +138,22 @@ useMicrosoftOpenTelemetry({ }); ``` +### A365 Configuration Coverage + +All A365 observability options are available through `a365`: + +| Option | Type | Default | Notes | +|---|---|---|---| +| `enabled` | `boolean` | `false` | Enables A365 exporter path | +| `tokenResolver` | `(agentId, tenantId) => string \| Promise` | — | Required when exporting to A365 | +| `clusterCategory` | `ClusterCategory` | `"prod"` | Same category values as Agent365-nodejs | +| `domainOverride` | `string` | — | Optional endpoint override | +| `authScopes` | `string[]` | `["https://api.powerplatform.com/.default"]` | Optional OAuth scopes override | +| `perRequestExport` | `boolean` | `false` | Export per trace when root span completes | +| `baggage.propagationEnabled` | `boolean` | `true` | Read baggage from incoming headers | +| `baggage.enrichSpans` | `boolean` | `true` | Copy baggage values onto span attributes | +| `hosting.enabled` | `boolean` | `false` | Turns on hosting middleware integration | + ## Environment Variables Environment variable names are **unchanged** from Agent365-nodejs: @@ -146,6 +171,43 @@ Environment variable names are **unchanged** from Agent365-nodejs: | `A365_PER_REQUEST_FLUSH_GRACE_MS` | Grace period after root span ends (default: `250`) | | `A365_PER_REQUEST_MAX_TRACE_AGE_MS` | Max trace age before forced flush (default: `1800000`) | +### Logging Level Configuration + +During migration, these environment variables control SDK diagnostics: + +| Environment Variable | Values | Behavior | +|---|---|---| +| `APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL` | `ALL`, `VERBOSE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `NONE` | Primary switch for OpenTelemetry diagnostics; also maps Azure logger levels for `VERBOSE`, `INFO`, `WARN`, `ERROR` | +| `OTEL_LOG_LEVEL` | `ALL`, `VERBOSE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `NONE` | Used when `APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL` is not set | +| `AZURE_LOG_LEVEL` | `verbose`, `info`, `warning`, `error` | Controls Azure logger level when the App Insights logging level variable is not mapped/absent | + +Example: + +```bash +set APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL=INFO +``` + +### Console Exporters During Migration + +You can keep local visibility while migrating by using console exporters. + +```typescript +useMicrosoftOpenTelemetry({ + a365: { + enabled: true, + tokenResolver: async (agentId, tenantId) => getToken(agentId, tenantId), + }, + enableConsoleExporters: true, // traces + metrics + logs to console +}); +``` + +Behavior summary: + +- `enableConsoleExporters: true`: always adds console exporters for traces, metrics, and logs. +- `enableConsoleExporters: false`: disables automatic console exporters. +- If **no** Azure Monitor/OTLP/A365 exporter is active, console exporters are auto-enabled. +- If `a365` options are provided but A365 is disabled (`a365.enabled` false/omitted), a span console exporter is added as a fallback so spans are still visible locally. + ## Scopes Scope usage is identical. Just update the import path: @@ -225,6 +287,26 @@ runWithExportToken(initialToken, async () => { }); ``` +## Hosting Middleware and Utilities + +If you previously used hosting helpers with Agent365, they are also exported from `@microsoft/opentelemetry`: + +- `BaggageMiddleware` +- `OutputLoggingMiddleware` +- `ObservabilityHostingManager` +- `BaggageBuilderUtils` +- `ScopeUtils` + +Use the same APIs with updated imports: + +```typescript +import { + BaggageMiddleware, + OutputLoggingMiddleware, + ObservabilityHostingManager, +} from "@microsoft/opentelemetry"; +``` + ## What's Not Migrated The following Agent365-nodejs components are **not** included in `@microsoft/opentelemetry` because they are runtime/hosting concerns rather than observability: @@ -246,4 +328,6 @@ The following Agent365-nodejs components are **not** included in `@microsoft/ope - [ ] Rename `SpanDetails` type references to `A365SpanDetails` - [ ] Rename `SpanProcessor` references to `A365SpanProcessor` - [ ] Verify environment variables work (names are unchanged) +- [ ] Set diagnostic logging level (`APPLICATIONINSIGHTS_INSTRUMENTATION_LOGGING_LEVEL` or `OTEL_LOG_LEVEL`) for migration validation +- [ ] Decide whether to force console exporters (`enableConsoleExporters`) during rollout/debugging - [ ] Remove `@microsoft/agents-a365-runtime` dependency if no longer needed diff --git a/README.md b/README.md index abdbf757..c62406c9 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,17 @@ That's it — traces, metrics, and logs are collected automatically with built-i | `views` | `ViewOptions[]` | — | Metric views | | `azureMonitor` | `AzureMonitorOpenTelemetryOptions` | — | Azure Monitor backend config. When provided, Azure Monitor export is enabled | | `a365` | `A365Options` | — | A365 observability config | +| `enableConsoleExporters` | `boolean` | auto | Enable console exporters for traces, metrics, and logs | ### `InstrumentationOptions` -Most instrumentations are enabled by default. Pass `{ enabled: false }` to disable individual instrumentations, or provide an `InstrumentationConfig` object to customize them. +Most instrumentations use `InstrumentationConfig` shape (`{ enabled?: boolean, ... }`). + +- Built-in infra instrumentations (`http`, `azureSdk`, `azureFunctions`, `mongoDb`, `mySql`, `postgreSql`, `redis`, `redis4`) are enabled by default. +- Logging instrumentations (`bunyan`, `winston`) are disabled by default. +- GenAI instrumentations (`openaiAgents`, `langchain`) are disabled by default. + +Set `enabled: true` or `enabled: false` explicitly for predictable behavior. | Key | Type | Default | Description | |---|---|---|---| @@ -85,8 +92,67 @@ Most instrumentations are enabled by default. Pass `{ enabled: false }` to disab | `redis4` | `InstrumentationConfig` | enabled | Redis 4 instrumentation | | `bunyan` | `InstrumentationConfig` | disabled | Bunyan log instrumentation | | `winston` | `InstrumentationConfig` | disabled | Winston log instrumentation | -| `openaiAgents` | `boolean | OpenAIAgentsInstrumentationConfig` | disabled | OpenAI Agents SDK instrumentation (requires `@openai/agents`) | -| `langchain` | `boolean | LangChainInstrumentationConfig` | disabled | LangChain instrumentation (requires `@langchain/core`) | +| `openaiAgents` | `OpenAIAgentsInstrumentationConfig` | disabled | OpenAI Agents SDK instrumentation (requires `@openai/agents`) | +| `langchain` | `LangChainInstrumentationConfig` | disabled | LangChain instrumentation (requires `@langchain/core`) | + +#### Turn instrumentations on/off + +```typescript +useMicrosoftOpenTelemetry({ + instrumentationOptions: { + // Disable specific built-in instrumentations + http: { enabled: false }, + redis: { enabled: false }, + + // Explicitly enable GenAI instrumentations + openaiAgents: { + enabled: true, + isContentRecordingEnabled: true, + }, + langchain: { + enabled: true, + isContentRecordingEnabled: true, + }, + }, +}); +``` + +Disable most built-in auto-instrumentation: + +```typescript +useMicrosoftOpenTelemetry({ + instrumentationOptions: { + http: { enabled: false }, + azureSdk: { enabled: false }, + azureFunctions: { enabled: false }, + mongoDb: { enabled: false }, + mySql: { enabled: false }, + postgreSql: { enabled: false }, + redis: { enabled: false }, + redis4: { enabled: false }, + bunyan: { enabled: false }, + winston: { enabled: false }, + openaiAgents: { enabled: false }, + langchain: { enabled: false }, + }, +}); +``` + +### Console exporters + +Use console exporters when validating local telemetry or debugging setup. + +```typescript +useMicrosoftOpenTelemetry({ + enableConsoleExporters: true, +}); +``` + +Behavior: + +- `enableConsoleExporters: true`: always enable console exporters (traces, metrics, logs). +- `enableConsoleExporters: false`: never auto-add console exporters. +- Omitted: console exporters auto-enable only when no other exporter path is active. ### `azureMonitor` options diff --git a/samples/src/langchainInstrumentation.ts b/samples/src/langchainInstrumentation.ts index 6234b073..e8350411 100644 --- a/samples/src/langchainInstrumentation.ts +++ b/samples/src/langchainInstrumentation.ts @@ -23,6 +23,7 @@ async function main(): Promise { }, instrumentationOptions: { langchain: { + enabled: true, isContentRecordingEnabled: true, }, }, diff --git a/samples/src/openaiInstrumentation.ts b/samples/src/openaiInstrumentation.ts index 0635974b..da9790e2 100644 --- a/samples/src/openaiInstrumentation.ts +++ b/samples/src/openaiInstrumentation.ts @@ -22,6 +22,7 @@ async function main(): Promise { }, instrumentationOptions: { openaiAgents: { + enabled: true, isContentRecordingEnabled: true, }, }, diff --git a/src/types.ts b/src/types.ts index 9b1d4ede..a2f90912 100644 --- a/src/types.ts +++ b/src/types.ts @@ -105,21 +105,21 @@ export interface InstrumentationOptions { /** * OpenAI Agents SDK instrumentation. - * Pass `true` for defaults or a configuration object. + * Uses InstrumentationConfig shape (`enabled`, etc.) plus OpenAI-specific options. * Requires `@openai/agents` as an optional peer dependency. */ - openaiAgents?: boolean | OpenAIAgentsInstrumentationConfig; + openaiAgents?: OpenAIAgentsInstrumentationConfig; /** * LangChain instrumentation. - * Pass `true` for defaults or a configuration object. + * Uses InstrumentationConfig shape (`enabled`, etc.) plus LangChain-specific options. * Requires `@langchain/core` as an optional peer dependency. */ - langchain?: boolean | LangChainInstrumentationConfig; + langchain?: LangChainInstrumentationConfig; } /** Configuration for OpenAI Agents SDK instrumentation. */ -export interface OpenAIAgentsInstrumentationConfig { +export interface OpenAIAgentsInstrumentationConfig extends InstrumentationConfig { /** Custom tracer name. */ tracerName?: string; /** Custom tracer version. */ @@ -138,7 +138,7 @@ export interface OpenAIAgentsInstrumentationConfig { } /** Configuration for LangChain instrumentation. */ -export interface LangChainInstrumentationConfig { +export interface LangChainInstrumentationConfig extends InstrumentationConfig { /** Enable recording of message content in spans. */ isContentRecordingEnabled?: boolean; } From 88beea38e50a5b49eefde6447c21e73540c090cc Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:45:28 -0700 Subject: [PATCH 2/8] style: apply prettier formatting --- src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index a2f90912..0d0b5a9a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -105,10 +105,10 @@ export interface InstrumentationOptions { /** * OpenAI Agents SDK instrumentation. - * Uses InstrumentationConfig shape (`enabled`, etc.) plus OpenAI-specific options. + * Uses InstrumentationConfig shape (`enabled`, etc.) plus OpenAI-specific options. * Requires `@openai/agents` as an optional peer dependency. */ - openaiAgents?: OpenAIAgentsInstrumentationConfig; + openaiAgents?: OpenAIAgentsInstrumentationConfig; /** * LangChain instrumentation. From 3d5b4cf0b29a2d2746e6a97f203b09edf8dc1e30 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:09:37 -0700 Subject: [PATCH 3/8] feat: wire all configuration options into runtime behavior; unify GenAI instrumentation interfaces and expand A365 config support - Wire GenAI instrumentation configs (openaiAgents, langchain) into distro initialization with lifecycle cleanup - Connect A365 configuration to runtime: pass authScopes to token resolver, auto-register hosting middleware - Wire tracerName/tracerVersion into OpenAI tracer initialization - Expand A365 migration guide and README with comprehensive configuration examples - Unify GenAI instrumentation interfaces to extend InstrumentationConfig like other instrumentations - Eliminate configuration no-ops by ensuring all options drive actual runtime behavior Closes: #44 --- CHANGELOG.md | 9 ++ MIGRATION_A365.md | 12 +-- README.md | 10 +- src/a365/configuration/A365Configuration.ts | 16 +++- .../configuration/A365ConfigurationOptions.ts | 20 +++- src/a365/exporter/Agent365Exporter.ts | 2 +- src/a365/exporter/Agent365ExporterOptions.ts | 6 ++ src/distro/distro.ts | 91 ++++++++++++++++++- .../openai/openAIAgentsTraceInstrumentor.ts | 5 +- 9 files changed, 153 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eea096a7..13545a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Release History +## [0.1.0-alpha.5] - 2026-04-24 + +### Features Added +- Wire GenAI instrumentation options into distro initialization (`openaiAgents`, `langchain`) with lifecycle cleanup on shutdown. ([#44](https://github.com/microsoft/opentelemetry-distro-javascript/pull/44)) +- Connect A365 configuration to runtime behavior: pass `authScopes` into token resolution and auto-register hosting middleware when `hosting.enabled` and `hosting.adapter` are provided. ([#44](https://github.com/microsoft/opentelemetry-distro-javascript/pull/44)) + +### Bugs Fixed +- Eliminate configuration no-ops by wiring `tracerName` / `tracerVersion` into OpenAI tracer initialization and aligning docs with actual runtime behavior. ([#44](https://github.com/microsoft/opentelemetry-distro-javascript/pull/44)) + ## [0.1.0-alpha.4] - 2026-04-22 ### Features Added diff --git a/MIGRATION_A365.md b/MIGRATION_A365.md index ac850d22..4418e99b 100644 --- a/MIGRATION_A365.md +++ b/MIGRATION_A365.md @@ -145,14 +145,14 @@ All A365 observability options are available through `a365`: | Option | Type | Default | Notes | |---|---|---|---| | `enabled` | `boolean` | `false` | Enables A365 exporter path | -| `tokenResolver` | `(agentId, tenantId) => string \| Promise` | — | Required when exporting to A365 | +| `tokenResolver` | `(agentId, tenantId, authScopes?) => string \| Promise` | — | Required when exporting to A365 | | `clusterCategory` | `ClusterCategory` | `"prod"` | Same category values as Agent365-nodejs | -| `domainOverride` | `string` | — | Optional endpoint override | -| `authScopes` | `string[]` | `["https://api.powerplatform.com/.default"]` | Optional OAuth scopes override | +| `domainOverride` | `string` | — | Optional endpoint override (applied by exporter) | +| `authScopes` | `string[]` | `["https://api.powerplatform.com/.default"]` | Passed to `tokenResolver` as the third argument | | `perRequestExport` | `boolean` | `false` | Export per trace when root span completes | -| `baggage.propagationEnabled` | `boolean` | `true` | Read baggage from incoming headers | -| `baggage.enrichSpans` | `boolean` | `true` | Copy baggage values onto span attributes | -| `hosting.enabled` | `boolean` | `false` | Turns on hosting middleware integration | +| `baggage.propagationEnabled` | `boolean` | `true` | Controls baggage middleware auto-registration when hosting is enabled | +| `baggage.enrichSpans` | `boolean` | `true` | Copy baggage values onto span attributes via `A365SpanProcessor` | +| `hosting.enabled` | `boolean` | `false` | Enables hosting middleware auto-registration when `hosting.adapter` is provided | ## Environment Variables diff --git a/README.md b/README.md index c62406c9..96c7ec64 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ useMicrosoftOpenTelemetry({ http: { enabled: false }, redis: { enabled: false }, - // Explicitly enable GenAI instrumentations + // Enable GenAI instrumentations openaiAgents: { enabled: true, isContentRecordingEnabled: true, @@ -151,8 +151,8 @@ useMicrosoftOpenTelemetry({ Behavior: - `enableConsoleExporters: true`: always enable console exporters (traces, metrics, logs). -- `enableConsoleExporters: false`: never auto-add console exporters. -- Omitted: console exporters auto-enable only when no other exporter path is active. +- `enableConsoleExporters: false`: do not auto-add the standard console exporters, except for the A365 span console fallback when `a365` options are provided but `a365.enabled` is `false` or omitted. +- Omitted: console exporters auto-enable only when no other exporter path is active; if `a365` options are provided but `a365.enabled` is `false` or omitted, the A365 span console fallback can still be added. ### `azureMonitor` options @@ -176,7 +176,7 @@ See the [OpenTelemetry OTLP Exporter specification](https://opentelemetry.io/doc | Option | Type | Default | Description | |---|---|---|---| | `enabled` | `boolean` | `false` | Enable A365 observability export | -| `tokenResolver` | `(agentId, tenantId) => string \| Promise` | — | Token resolver for A365 service authentication | +| `tokenResolver` | `(agentId, tenantId, authScopes?) => string \| Promise` | — | Token resolver for A365 service authentication | | `clusterCategory` | `ClusterCategory` | `"prod"` | Cluster category for endpoint resolution (`local`, `dev`, `test`, `preprod`, `firstrelease`, `prod`, `gov`, `high`, `dod`, `mooncake`, `ex`, `rx`) | | `domainOverride` | `string` | — | Override the A365 observability service domain | | `authScopes` | `string[]` | `["https://api.powerplatform.com/.default"]` | OAuth scopes for A365 service authentication | @@ -196,6 +196,8 @@ See the [OpenTelemetry OTLP Exporter specification](https://opentelemetry.io/doc | Option | Type | Default | Description | |---|---|---|---| | `enabled` | `boolean` | `false` | Enable hosting middleware integration (baggage middleware, output logging, etc.) | +| `adapter` | `{ use(...middlewares): void }` | — | Adapter instance where middleware is auto-registered when `enabled` is true | +| `enableOutputLogging` | `boolean` | `true` | Enable output logging middleware auto-registration | #### A365 environment variables diff --git a/src/a365/configuration/A365Configuration.ts b/src/a365/configuration/A365Configuration.ts index 1ce1909a..f272030b 100644 --- a/src/a365/configuration/A365Configuration.ts +++ b/src/a365/configuration/A365Configuration.ts @@ -5,7 +5,6 @@ import type { A365Options, ClusterCategory, A365BaggageOptions, - A365HostingOptions, } from "./A365ConfigurationOptions.js"; import { Logger } from "../../shared/logging/index.js"; import { JsonConfig } from "../../shared/jsonConfig.js"; @@ -73,7 +72,11 @@ export class A365Configuration { public readonly enabled: boolean; /** Token resolver callback for A365 service authentication. */ - public readonly tokenResolver?: (agentId: string, tenantId: string) => string | Promise; + public readonly tokenResolver?: ( + agentId: string, + tenantId: string, + authScopes?: string[], + ) => string | Promise; /** Cluster category. */ public readonly clusterCategory: ClusterCategory; @@ -91,7 +94,11 @@ export class A365Configuration { public readonly baggage: Required; /** Hosting options. */ - public readonly hosting: Required; + public readonly hosting: { + enabled: boolean; + adapter?: { use(...middlewares: unknown[]): void }; + enableOutputLogging: boolean; + }; constructor(options?: A365Options) { // 1. Set defaults @@ -166,6 +173,9 @@ export class A365Configuration { this.hosting = { enabled: jsonA365?.hosting?.enabled ?? options?.hosting?.enabled ?? false, + adapter: jsonA365?.hosting?.adapter ?? options?.hosting?.adapter, + enableOutputLogging: + jsonA365?.hosting?.enableOutputLogging ?? options?.hosting?.enableOutputLogging ?? true, }; // Warn when A365-scoped options are set but A365 is not enabled diff --git a/src/a365/configuration/A365ConfigurationOptions.ts b/src/a365/configuration/A365ConfigurationOptions.ts index 29828dcf..1d9986a8 100644 --- a/src/a365/configuration/A365ConfigurationOptions.ts +++ b/src/a365/configuration/A365ConfigurationOptions.ts @@ -31,10 +31,14 @@ export interface A365Options { /** * Token resolver for authenticating with the A365 observability service. - * Called with (agentId, tenantId) extracted from span attributes. + * Called with (agentId, tenantId, authScopes) extracted from span attributes/config. * Must return a bearer token string or a promise resolving to one. */ - tokenResolver?: (agentId: string, tenantId: string) => string | Promise; + tokenResolver?: ( + agentId: string, + tenantId: string, + authScopes?: string[], + ) => string | Promise; /** Cluster category for the A365 service endpoint. */ clusterCategory?: ClusterCategory; @@ -74,4 +78,16 @@ export interface A365HostingOptions { * Requires `@microsoft/agents-hosting` as an optional peer dependency. */ enabled?: boolean; + + /** + * Adapter instance where hosting middleware will be auto-registered. + * Must expose a `use(...middlewares)` method compatible with agents-hosting adapters. + */ + adapter?: { use(...middlewares: unknown[]): void }; + + /** + * Enable output logging middleware auto-registration when hosting is enabled. + * @default true + */ + enableOutputLogging?: boolean; } diff --git a/src/a365/exporter/Agent365Exporter.ts b/src/a365/exporter/Agent365Exporter.ts index f0b65c12..ea039f31 100644 --- a/src/a365/exporter/Agent365Exporter.ts +++ b/src/a365/exporter/Agent365Exporter.ts @@ -159,7 +159,7 @@ export class Agent365Exporter implements SpanExporter { private async resolveToken(agentId: string, tenantId: string): Promise { if (!this.options.tokenResolver) return null; - const result = this.options.tokenResolver(agentId, tenantId); + const result = this.options.tokenResolver(agentId, tenantId, this.options.authScopes); return result instanceof Promise ? result : result; } diff --git a/src/a365/exporter/Agent365ExporterOptions.ts b/src/a365/exporter/Agent365ExporterOptions.ts index 419071d1..79514c42 100644 --- a/src/a365/exporter/Agent365ExporterOptions.ts +++ b/src/a365/exporter/Agent365ExporterOptions.ts @@ -10,6 +10,7 @@ import type { ClusterCategory } from "../configuration/A365ConfigurationOptions. export type TokenResolver = ( agentId: string, tenantId: string, + authScopes?: string[], ) => string | null | Promise; /** @@ -28,6 +29,9 @@ export interface Agent365ExporterOptions { /** Override the A365 observability service domain. */ domainOverride?: string; + /** OAuth scopes used during token resolution. */ + authScopes?: string[]; + /** Maximum span queue size before drops occur. @default 2048 */ maxQueueSize?: number; @@ -50,6 +54,7 @@ export class ResolvedExporterOptions { public readonly tokenResolver?: TokenResolver; public readonly useS2SEndpoint: boolean; public readonly domainOverride?: string; + public readonly authScopes: string[]; public readonly maxQueueSize: number; public readonly scheduledDelayMilliseconds: number; public readonly exporterTimeoutMilliseconds: number; @@ -61,6 +66,7 @@ export class ResolvedExporterOptions { this.tokenResolver = options?.tokenResolver; this.useS2SEndpoint = options?.useS2SEndpoint ?? false; this.domainOverride = options?.domainOverride; + this.authScopes = options?.authScopes ?? ["https://api.powerplatform.com/.default"]; this.maxQueueSize = options?.maxQueueSize ?? 2048; this.scheduledDelayMilliseconds = options?.scheduledDelayMilliseconds ?? 5000; this.exporterTimeoutMilliseconds = options?.exporterTimeoutMilliseconds ?? 90000; diff --git a/src/distro/distro.ts b/src/distro/distro.ts index 877b2fd0..ce52ba5b 100644 --- a/src/distro/distro.ts +++ b/src/distro/distro.ts @@ -34,10 +34,17 @@ import { Agent365Exporter, A365SpanProcessor, PerRequestSpanProcessor, + ObservabilityHostingManager, } from "../a365/index.js"; -import type { MicrosoftOpenTelemetryOptions } from "../types.js"; +import type { + MicrosoftOpenTelemetryOptions, + InstrumentationOptions, + OpenAIAgentsInstrumentationConfig, + LangChainInstrumentationConfig, +} from "../types.js"; import { MICROSOFT_OPENTELEMETRY_VERSION } from "../types.js"; import { createInstrumentations, createSampler, createViews } from "./instrumentations.js"; +import { Logger } from "../shared/logging/index.js"; process.env["AZURE_MONITOR_DISTRO_VERSION"] = AZURE_MONITOR_OPENTELEMETRY_VERSION; process.env["MICROSOFT_OPENTELEMETRY_VERSION"] = MICROSOFT_OPENTELEMETRY_VERSION; @@ -61,6 +68,7 @@ let disposeAzureMonitor: (() => void) | undefined; export function useMicrosoftOpenTelemetry(options?: MicrosoftOpenTelemetryOptions): void { const config = new InternalConfig(options); patchOpenTelemetryInstrumentationEnable(); + initializeGenAIInstrumentations(options?.instrumentationOptions); // Azure Monitor is enabled when configured programmatically or via JSON config. // An explicit `enabled: false` always wins, even if a connection string is present. @@ -146,6 +154,7 @@ export function useMicrosoftOpenTelemetry(options?: MicrosoftOpenTelemetryOption const a365Exporter = new Agent365Exporter({ clusterCategory: a365Config.clusterCategory, domainOverride: a365Config.domainOverride, + authScopes: a365Config.authScopes, tokenResolver: a365Config.tokenResolver, }); // A365SpanProcessor copies baggage (tenant, agent, session, etc.) to span attributes @@ -158,6 +167,21 @@ export function useMicrosoftOpenTelemetry(options?: MicrosoftOpenTelemetryOption ? new PerRequestSpanProcessor(a365Exporter) : new BatchSpanProcessor(a365Exporter); spanProcessors.push(a365ExportProcessor); + + if (a365Config.hosting.enabled) { + if (a365Config.hosting.adapter) { + const hostingManager = new ObservabilityHostingManager(); + hostingManager.configure(a365Config.hosting.adapter, { + enableBaggage: a365Config.baggage.propagationEnabled, + enableOutputLogging: a365Config.hosting.enableOutputLogging, + }); + } else { + Logger.getInstance().warn( + "[A365] hosting.enabled is true but no hosting.adapter was provided. " + + "Pass a365.hosting.adapter to auto-register middleware.", + ); + } + } } else if (a365ConsoleExportFallback) { // A365 options provided but exporter disabled — fall back to console export // so developers can validate spans locally (matches upstream A365 SDK behavior @@ -223,6 +247,7 @@ export function useMicrosoftOpenTelemetry(options?: MicrosoftOpenTelemetryOption */ export function shutdownMicrosoftOpenTelemetry(): Promise { disposeAzureMonitor?.(); + void resetGenAIInstrumentations(); return sdk?.shutdown(); } @@ -234,3 +259,67 @@ export function shutdownMicrosoftOpenTelemetry(): Promise { export function _getSdkInstance(): NodeSDK | undefined { return sdk; } + +function initializeGenAIInstrumentations(options?: InstrumentationOptions): void { + const openAIOptions = options?.openaiAgents; + if (openAIOptions && openAIOptions.enabled !== false) { + void initializeOpenAIAgentsInstrumentation(openAIOptions); + } + + const langChainOptions = options?.langchain; + if (langChainOptions && langChainOptions.enabled !== false) { + void initializeLangChainInstrumentation(langChainOptions); + } +} + +async function initializeOpenAIAgentsInstrumentation( + options: OpenAIAgentsInstrumentationConfig, +): Promise { + try { + const { OpenAIAgentsTraceInstrumentor } = + await import("../genai/instrumentations/openai/openAIAgentsTraceInstrumentor.js"); + OpenAIAgentsTraceInstrumentor.instrument(options); + } catch (error) { + Logger.getInstance().warn( + "[GenAI] Failed to initialize OpenAI Agents instrumentation. " + + "Ensure @openai/agents is installed when openaiAgents config is enabled.", + error, + ); + } +} + +async function initializeLangChainInstrumentation( + options: LangChainInstrumentationConfig, +): Promise { + try { + const [{ LangChainTraceInstrumentor }, callbackManagerModule] = await Promise.all([ + import("../genai/instrumentations/langchain/langchainTraceInstrumentor.js"), + import("@langchain/core/callbacks/manager"), + ]); + LangChainTraceInstrumentor.instrument(callbackManagerModule, options); + } catch (error) { + Logger.getInstance().warn( + "[GenAI] Failed to initialize LangChain instrumentation. " + + "Ensure @langchain/core is installed when langchain config is enabled.", + error, + ); + } +} + +async function resetGenAIInstrumentations(): Promise { + try { + const { OpenAIAgentsTraceInstrumentor } = + await import("../genai/instrumentations/openai/openAIAgentsTraceInstrumentor.js"); + OpenAIAgentsTraceInstrumentor.resetInstance(); + } catch { + // Ignore when optional dependency is not installed. + } + + try { + const { LangChainTraceInstrumentor } = + await import("../genai/instrumentations/langchain/langchainTraceInstrumentor.js"); + LangChainTraceInstrumentor.resetInstance(); + } catch { + // Ignore when optional dependency is not installed. + } +} diff --git a/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts b/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts index 7be586fa..627e7859 100644 --- a/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts +++ b/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts @@ -48,7 +48,10 @@ class OpenAIAgentsTraceInstrumentorImpl extends InstrumentationBase Date: Thu, 23 Apr 2026 12:14:27 -0700 Subject: [PATCH 4/8] fix: import OpenAIAgentsInstrumentationConfig from types to include tracerName/tracerVersion The local interface definition in openAIAgentsTraceInstrumentor.ts was shadowing the actual type from src/types.ts which includes tracerName and tracerVersion properties. This caused TypeScript compilation errors. Import the type from src/types.ts and remove the incomplete local definition. --- .../openai/openAIAgentsTraceInstrumentor.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts b/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts index 627e7859..657821b7 100644 --- a/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts +++ b/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts @@ -10,25 +10,9 @@ import { InstrumentationModuleDefinition, } from "@opentelemetry/instrumentation"; import { setTraceProcessors, setTracingDisabled, TracingProcessor } from "@openai/agents"; +import type { OpenAIAgentsInstrumentationConfig } from "../../../types.js"; import { OpenAIAgentsTraceProcessor } from "./openAIAgentsTraceProcessor.js"; -/** - * Configuration options for the OpenAI Agents instrumentor. - */ -export interface OpenAIAgentsInstrumentationConfig extends InstrumentationConfig { - /** - * When true, the gen_ai.input.messages attribute containing LLM input - * messages will be suppressed and not attached to spans in InvokeAgent scopes. - * @default false - */ - suppressInvokeAgentInput?: boolean; - /** - * Whether to enable content recording (input/output messages, tool args, etc.). - * @default false - */ - isContentRecordingEnabled?: boolean; -} - /** * Internal singleton implementation. */ From 987d0fdf0eaefbdac609e42e72361def70d4b80f Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Thu, 23 Apr 2026 14:30:51 -0700 Subject: [PATCH 5/8] fix: remove unused InstrumentationConfig import The import was leftover from removing the local interface definition. OpenAIAgentsInstrumentationConfig is imported from src/types.js which already extends InstrumentationConfig, so this import is unnecessary. --- .../instrumentations/openai/openAIAgentsTraceInstrumentor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts b/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts index 657821b7..8ce25fa8 100644 --- a/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts +++ b/src/genai/instrumentations/openai/openAIAgentsTraceInstrumentor.ts @@ -6,7 +6,6 @@ import { diag, trace, Tracer } from "@opentelemetry/api"; import { InstrumentationBase, - InstrumentationConfig, InstrumentationModuleDefinition, } from "@opentelemetry/instrumentation"; import { setTraceProcessors, setTracingDisabled, TracingProcessor } from "@openai/agents"; From 99a5ca80a0bac46a3df120c1374eabacc7f4edf1 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:24:04 -0700 Subject: [PATCH 6/8] Update --- README.md | 6 +++--- src/shared/config.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 33922454..24ad80c1 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Most instrumentations use `InstrumentationConfig` shape (`{ enabled?: boolean, . - Built-in infra instrumentations (`http`, `azureSdk`, `azureFunctions`, `mongoDb`, `mySql`, `postgreSql`, `redis`, `redis4`) are enabled by default. - Logging instrumentations (`bunyan`, `winston`) are disabled by default. -- GenAI instrumentations (`openaiAgents`, `langchain`) are disabled by default. +- GenAI instrumentations (`openaiAgents`, `langchain`) are enabled by default. Set `enabled: true` or `enabled: false` explicitly for predictable behavior. @@ -91,8 +91,8 @@ Set `enabled: true` or `enabled: false` explicitly for predictable behavior. | `redis4` | `InstrumentationConfig` | enabled | Redis 4 instrumentation | | `bunyan` | `InstrumentationConfig` | disabled | Bunyan log instrumentation | | `winston` | `InstrumentationConfig` | disabled | Winston log instrumentation | -| `openaiAgents` | `OpenAIAgentsInstrumentationConfig` | disabled | OpenAI Agents SDK instrumentation (requires `@openai/agents`) | -| `langchain` | `LangChainInstrumentationConfig` | disabled | LangChain instrumentation (requires `@langchain/core`) | +| `openaiAgents` | `OpenAIAgentsInstrumentationConfig` | enabled | OpenAI Agents SDK instrumentation (requires `@openai/agents`) | +| `langchain` | `LangChainInstrumentationConfig` | enabled | LangChain instrumentation (requires `@langchain/core`) | #### Turn instrumentations on/off diff --git a/src/shared/config.ts b/src/shared/config.ts index 55d6a324..308685c7 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -90,6 +90,8 @@ export class InternalConfig { postgreSql: { enabled: true }, redis: { enabled: true }, redis4: { enabled: true }, + openaiAgents: { enabled: true }, + langchain: { enabled: true }, }; this._setDefaultResource(); this.browserSdkLoaderOptions = { From 1219d8506355472bd25b8774253b4abeb90f10b5 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:17:20 -0700 Subject: [PATCH 7/8] Remove JSON configuration support from distro (#49) * Remove JSON configuration support from distro Stop reading applicationinsights.json and remove JsonConfig from shared and A365 configuration flows.\n\nUpdate tests to reflect programmatic/env-only precedence, remove obsolete JsonConfig tests/snippets, and document the breaking change in the changelog. * Fix misleading config validation test assertion --- CHANGELOG.md | 5 +- src/a365/configuration/A365Configuration.ts | 32 +-- src/shared/config.ts | 47 ----- src/shared/jsonConfig.ts | 135 ------------- .../unit/a365/a365Configuration.test.ts | 53 ----- test/internal/unit/shared/config.test.ts | 187 ++++++++---------- test/internal/unit/shared/jsonConfig.test.ts | 159 --------------- test/snippets.spec.ts | 6 - 8 files changed, 96 insertions(+), 528 deletions(-) delete mode 100644 src/shared/jsonConfig.ts delete mode 100644 test/internal/unit/shared/jsonConfig.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 282fcc0b..9debb3f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Breaking Changes - Remove Azure Functions auto-instrumentation support from this package. The `instrumentationOptions.azureFunctions` option is no longer available. +- Remove JSON configuration support (`applicationinsights.json`, `APPLICATIONINSIGHTS_CONFIGURATION_FILE`, and `APPLICATIONINSIGHTS_CONFIGURATION_CONTENT`). Configuration now comes only from programmatic options and environment variables. ### Features Added - Wire GenAI instrumentation options into distro initialization (`openaiAgents`, `langchain`) with lifecycle cleanup on shutdown. ([#44](https://github.com/microsoft/opentelemetry-distro-javascript/pull/44)) @@ -11,8 +12,8 @@ ### Bugs Fixed - Prevent ESM/CJS interop regressions by removing the problematic Azure Functions instrumentation path and adding explicit built-ESM import regression coverage. -- - Eliminate configuration no-ops by wiring `tracerName` / `tracerVersion` into OpenAI tracer initialization and aligning docs with actual runtime behavior. ([#44](https://github.com/microsoft/opentelemetry-distro-javascript/pull/44)) - +- Eliminate configuration no-ops by wiring `tracerName` / `tracerVersion` into OpenAI tracer initialization and aligning docs with actual runtime behavior. ([#44](https://github.com/microsoft/opentelemetry-distro-javascript/pull/44)) +- Remove startup noise caused by implicit JSON config file probing in the Microsoft distro. ### Other Changes - Expand PR validation checks to run unit tests, functional tests, and a built ESM import smoke test. diff --git a/src/a365/configuration/A365Configuration.ts b/src/a365/configuration/A365Configuration.ts index f272030b..f05c38da 100644 --- a/src/a365/configuration/A365Configuration.ts +++ b/src/a365/configuration/A365Configuration.ts @@ -7,7 +7,6 @@ import type { A365BaggageOptions, } from "./A365ConfigurationOptions.js"; import { Logger } from "../../shared/logging/index.js"; -import { JsonConfig } from "../../shared/jsonConfig.js"; /** * Parse an environment variable as a boolean. @@ -61,11 +60,10 @@ const VALID_CLUSTER_CATEGORIES: ReadonlySet = new Set([ /** * Resolved A365 configuration. * - * Merges values from four sources (lowest to highest precedence): + * Merges values from three sources (lowest to highest precedence): * 1. Defaults * 2. Programmatic options (`A365Options`) - * 3. JSON config (`applicationinsights.json` → `a365` key) - * 4. Environment variables (see `A365_ENV_VARS`) + * 3. Environment variables (see `A365_ENV_VARS`) */ export class A365Configuration { /** Whether A365 observability is enabled. */ @@ -115,19 +113,7 @@ export class A365Configuration { perRequestExport = options.perRequestExport ?? perRequestExport; } - // 3. Apply JSON config (takes precedence over programmatic options) - const jsonA365 = JsonConfig.getInstance().a365; - if (jsonA365) { - enabled = jsonA365.enabled ?? enabled; - clusterCategory = jsonA365.clusterCategory ?? clusterCategory; - domainOverride = jsonA365.domainOverride ?? domainOverride; - perRequestExport = jsonA365.perRequestExport ?? perRequestExport; - if (jsonA365.authScopes) { - authScopes = jsonA365.authScopes; - } - } - - // 4. Apply environment variable overrides (highest precedence) + // 3. Apply environment variable overrides (highest precedence) const envEnabled = parseEnvBoolean(process.env[A365_ENV_VARS.EXPORTER_ENABLED]); if (envEnabled !== undefined) { enabled = envEnabled; @@ -166,16 +152,14 @@ export class A365Configuration { this.perRequestExport = perRequestExport; this.baggage = { - propagationEnabled: - jsonA365?.baggage?.propagationEnabled ?? options?.baggage?.propagationEnabled ?? true, - enrichSpans: jsonA365?.baggage?.enrichSpans ?? options?.baggage?.enrichSpans ?? true, + propagationEnabled: options?.baggage?.propagationEnabled ?? true, + enrichSpans: options?.baggage?.enrichSpans ?? true, }; this.hosting = { - enabled: jsonA365?.hosting?.enabled ?? options?.hosting?.enabled ?? false, - adapter: jsonA365?.hosting?.adapter ?? options?.hosting?.adapter, - enableOutputLogging: - jsonA365?.hosting?.enableOutputLogging ?? options?.hosting?.enableOutputLogging ?? true, + enabled: options?.hosting?.enabled ?? false, + adapter: options?.hosting?.adapter, + enableOutputLogging: options?.hosting?.enableOutputLogging ?? true, }; // Warn when A365-scoped options are set but A365 is not enabled diff --git a/src/shared/config.ts b/src/shared/config.ts index 308685c7..c6f4730b 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -16,8 +16,6 @@ import type { import type { Sampler } from "@opentelemetry/sdk-trace-base"; import type { AzureMonitorExporterOptions } from "@azure/monitor-opentelemetry-exporter"; import { EnvConfig } from "./envConfig.js"; -import { JsonConfig } from "./jsonConfig.js"; -import { Logger } from "./logging/index.js"; import { azureAksDetector, azureAppServiceDetector, @@ -140,8 +138,6 @@ export class InternalConfig { : this.enablePerformanceCounters; } } - // JSON configuration will take precedence over options provided - this._mergeJsonConfig(); // ENV configuration will take precedence over other configurations this._mergeEnvConfig(); } @@ -155,49 +151,6 @@ export class InternalConfig { this.sampler = envConfig.sampler ?? this.sampler; } - private _mergeJsonConfig(): void { - try { - const jsonConfig = JsonConfig.getInstance(); - // Global options - this.samplingRatio = - jsonConfig.samplingRatio !== undefined ? jsonConfig.samplingRatio : this.samplingRatio; - this.tracesPerSecond = - jsonConfig.tracesPerSecond !== undefined - ? jsonConfig.tracesPerSecond - : this.tracesPerSecond; - this.instrumentationOptions = Object.assign( - this.instrumentationOptions, - jsonConfig.instrumentationOptions, - ); - // Azure Monitor-scoped options - const azureMonitor = jsonConfig.azureMonitor; - if (azureMonitor) { - this.browserSdkLoaderOptions = Object.assign( - this.browserSdkLoaderOptions, - azureMonitor.browserSdkLoaderOptions, - ); - this.enableLiveMetrics = - azureMonitor.enableLiveMetrics !== undefined - ? azureMonitor.enableLiveMetrics - : this.enableLiveMetrics; - this.enableStandardMetrics = - azureMonitor.enableStandardMetrics !== undefined - ? azureMonitor.enableStandardMetrics - : this.enableStandardMetrics; - this.enableTraceBasedSamplingForLogs = - azureMonitor.enableTraceBasedSamplingForLogs !== undefined - ? azureMonitor.enableTraceBasedSamplingForLogs - : this.enableTraceBasedSamplingForLogs; - this.azureMonitorExporterOptions = Object.assign( - this.azureMonitorExporterOptions, - azureMonitor.azureMonitorExporterOptions, - ); - } - } catch (error) { - Logger.getInstance().error("Failed to load JSON config file values.", error); - } - } - private _setDefaultResource(): void { let resource = defaultResource(); // Load resource attributes from env diff --git a/src/shared/jsonConfig.ts b/src/shared/jsonConfig.ts deleted file mode 100644 index d8607f6b..00000000 --- a/src/shared/jsonConfig.ts +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import fs from "node:fs"; -import path from "node:path"; -import type { - AzureMonitorOpenTelemetryOptions, - BrowserSdkLoaderOptions, - InstrumentationOptions, -} from "../types.js"; -import type { A365Options } from "../a365/index.js"; -import type { AzureMonitorExporterOptions } from "@azure/monitor-opentelemetry-exporter"; -import type { MicrosoftOpenTelemetryOptions } from "../types.js"; -import { Logger } from "./logging/index.js"; -import { dirName } from "./module.js"; - -/** - * Walk up from a starting directory until a directory containing package.json is found. - * Falls back to the starting directory if no package.json is located. - * @internal - */ -function findPackageRoot(startDir: string): string { - let current = path.resolve(startDir); - while (true) { - if (fs.existsSync(path.join(current, "package.json"))) { - return current; - } - const parent = path.dirname(current); - if (parent === current) { - // Reached filesystem root without finding package.json - return startDir; - } - current = parent; - } -} - -const ENV_CONFIGURATION_FILE = "APPLICATIONINSIGHTS_CONFIGURATION_FILE"; -const ENV_CONTENT = "APPLICATIONINSIGHTS_CONFIGURATION_CONTENT"; - -/** - * Azure Monitor OpenTelemetry Client Configuration through JSON File. - * - * Implements {@link MicrosoftOpenTelemetryOptions}. - * Azure Monitor-specific fields are grouped under `azureMonitor`. - * The JSON file format remains flat for backward compatibility; the constructor - * maps the flat structure to the nested shape. - * @internal - */ -export class JsonConfig implements MicrosoftOpenTelemetryOptions { - /** The rate of telemetry items tracked that should be transmitted (Default 1.0) */ - public samplingRatio?: number; - /** The maximum number of spans to sample per second. */ - public tracesPerSecond?: number; - /** - * OpenTelemetry Instrumentations configuration included as part of Azure Monitor (azureSdk, http, mongoDb, mySql, postgreSql, redis, redis4) - */ - public instrumentationOptions?: InstrumentationOptions; - /** Azure Monitor scoped options */ - public azureMonitor?: AzureMonitorOpenTelemetryOptions; - /** A365 scoped options */ - public a365?: A365Options; - - private static _instance: JsonConfig; - - private _tempDir: string; - - /** Get Singleton instance */ - public static getInstance(): JsonConfig { - if (!JsonConfig._instance) { - JsonConfig._instance = new JsonConfig(); - } - return JsonConfig._instance; - } - - /** - * Initializes a new instance of the JsonConfig class. - */ - constructor() { - let jsonString = ""; - this._tempDir = ""; - const contentJsonConfig = process.env[ENV_CONTENT]; - // JSON string added directly in env variable - if (contentJsonConfig) { - jsonString = contentJsonConfig; - } - // JSON file - else { - const configFileName = "applicationinsights.json"; - const rootPath = findPackageRoot(dirName()); - this._tempDir = path.join(rootPath, configFileName); // default - const configFile = process.env[ENV_CONFIGURATION_FILE]; - if (configFile) { - if (path.isAbsolute(configFile)) { - this._tempDir = configFile; - } else { - this._tempDir = path.join(rootPath, configFile); // Relative path to applicationinsights folder - } - } - try { - jsonString = fs.readFileSync(this._tempDir, "utf8"); - } catch (err) { - Logger.getInstance().info("Failed to read JSON config file: ", err); - } - } - try { - const jsonConfig = JSON.parse(jsonString) as Record; - // Global options - this.samplingRatio = jsonConfig.samplingRatio as number | undefined; - this.tracesPerSecond = jsonConfig.tracesPerSecond as number | undefined; - this.instrumentationOptions = jsonConfig.instrumentationOptions as - | InstrumentationOptions - | undefined; - // Azure Monitor-scoped options (flat JSON → nested structure) - this.azureMonitor = { - azureMonitorExporterOptions: jsonConfig.azureMonitorExporterOptions as - | AzureMonitorExporterOptions - | undefined, - browserSdkLoaderOptions: jsonConfig.browserSdkLoaderOptions as - | BrowserSdkLoaderOptions - | undefined, - enableLiveMetrics: jsonConfig.enableLiveMetrics as boolean | undefined, - enableStandardMetrics: jsonConfig.enableStandardMetrics as boolean | undefined, - enableTraceBasedSamplingForLogs: jsonConfig.enableTraceBasedSamplingForLogs as - | boolean - | undefined, - }; - // A365-scoped options - if (jsonConfig.a365 && typeof jsonConfig.a365 === "object") { - this.a365 = jsonConfig.a365 as A365Options; - } - } catch (err) { - Logger.getInstance().info("Missing or invalid JSON config file: ", err); - } - } -} diff --git a/test/internal/unit/a365/a365Configuration.test.ts b/test/internal/unit/a365/a365Configuration.test.ts index 59a3a37d..a4ed67bb 100644 --- a/test/internal/unit/a365/a365Configuration.test.ts +++ b/test/internal/unit/a365/a365Configuration.test.ts @@ -6,19 +6,16 @@ import { A365Configuration, A365_ENV_VARS, } from "../../../../src/a365/configuration/A365Configuration.js"; -import { JsonConfig } from "../../../../src/shared/jsonConfig.js"; describe("A365Configuration", () => { let originalEnv: NodeJS.ProcessEnv; beforeEach(() => { originalEnv = { ...process.env }; - (JsonConfig as any)["_instance"] = undefined; }); afterEach(() => { process.env = originalEnv; - (JsonConfig as any)["_instance"] = undefined; vi.restoreAllMocks(); }); @@ -149,56 +146,6 @@ describe("A365Configuration", () => { }); }); - describe("JSON config", () => { - it("should apply A365 options from JSON config", () => { - process.env["APPLICATIONINSIGHTS_CONFIGURATION_CONTENT"] = JSON.stringify({ - a365: { - enabled: true, - clusterCategory: "preprod", - domainOverride: "json.example.com", - perRequestExport: true, - }, - }); - const config = new A365Configuration(); - assert.strictEqual(config.enabled, true); - assert.strictEqual(config.clusterCategory, "preprod"); - assert.strictEqual(config.domainOverride, "json.example.com"); - assert.strictEqual(config.perRequestExport, true); - }); - - it("JSON config takes precedence over programmatic options", () => { - process.env["APPLICATIONINSIGHTS_CONFIGURATION_CONTENT"] = JSON.stringify({ - a365: { clusterCategory: "gov" }, - }); - const config = new A365Configuration({ clusterCategory: "prod" }); - assert.strictEqual(config.clusterCategory, "gov"); - }); - - it("env vars take precedence over JSON config", () => { - process.env["APPLICATIONINSIGHTS_CONFIGURATION_CONTENT"] = JSON.stringify({ - a365: { enabled: true, clusterCategory: "preprod" }, - }); - process.env[A365_ENV_VARS.EXPORTER_ENABLED] = "false"; - process.env[A365_ENV_VARS.CLUSTER_CATEGORY] = "gov"; - const config = new A365Configuration(); - assert.strictEqual(config.enabled, false); - assert.strictEqual(config.clusterCategory, "gov"); - }); - - it("should apply baggage and hosting from JSON config", () => { - process.env["APPLICATIONINSIGHTS_CONFIGURATION_CONTENT"] = JSON.stringify({ - a365: { - baggage: { propagationEnabled: false, enrichSpans: false }, - hosting: { enabled: true }, - }, - }); - const config = new A365Configuration(); - assert.strictEqual(config.baggage.propagationEnabled, false); - assert.strictEqual(config.baggage.enrichSpans, false); - assert.strictEqual(config.hosting.enabled, true); - }); - }); - describe("precedence", () => { it("env vars take precedence over programmatic options", () => { process.env[A365_ENV_VARS.CLUSTER_CATEGORY] = "preprod"; diff --git a/test/internal/unit/shared/config.test.ts b/test/internal/unit/shared/config.test.ts index e21f4b8c..4653a799 100644 --- a/test/internal/unit/shared/config.test.ts +++ b/test/internal/unit/shared/config.test.ts @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import path from "node:path"; - import { InternalConfig } from "../../../../src/shared/index.js"; -import { JsonConfig } from "../../../../src/shared/jsonConfig.js"; import { resourceFromAttributes } from "@opentelemetry/resources"; import { CloudPlatformValues, @@ -39,71 +36,83 @@ describe("Library/Config", () => { afterEach(() => { vi.unstubAllEnvs(); vi.restoreAllMocks(); - (JsonConfig["_instance"] as any) = undefined; }); describe("#constructor", () => { - it("merge JSON config", () => { - (JsonConfig["_instance"] as any) = undefined; - const customConfigJSONPath = path.resolve(__dirname, "config.json"); - vi.stubEnv("APPLICATIONINSIGHTS_CONFIGURATION_FILE", customConfigJSONPath); - const config = new InternalConfig(); + it("merge programmatic options", () => { + const options: MicrosoftOpenTelemetryOptions = { + samplingRatio: 0.3, + tracesPerSecond: 0.2, + instrumentationOptions: { + azureSdk: { enabled: true }, + mongoDb: { enabled: true }, + mySql: { enabled: true }, + postgreSql: { enabled: true }, + redis: { enabled: true }, + redis4: { enabled: true }, + }, + azureMonitor: { + azureMonitorExporterOptions: { + connectionString: + "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/", + disableOfflineStorage: true, + storageDirectory: "testPath", + }, + }, + }; + const mergedConfig = new InternalConfig(options); assert.deepStrictEqual( - config.azureMonitorExporterOptions.connectionString, + mergedConfig.azureMonitorExporterOptions.connectionString, "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/", ); - assert.deepStrictEqual(config.samplingRatio, 0.3, "Wrong samplingRatio"); - assert.deepStrictEqual(config.tracesPerSecond, 0.2, "Wrong tracesPerSecond"); + assert.deepStrictEqual(mergedConfig.samplingRatio, 0.3, "Wrong samplingRatio"); + assert.deepStrictEqual(mergedConfig.tracesPerSecond, 0.2, "Wrong tracesPerSecond"); assert.deepStrictEqual( - config.azureMonitorExporterOptions?.disableOfflineStorage, + mergedConfig.azureMonitorExporterOptions?.disableOfflineStorage, true, "Wrong disableOfflineStorage", ); assert.deepStrictEqual( - config.azureMonitorExporterOptions?.storageDirectory, + mergedConfig.azureMonitorExporterOptions?.storageDirectory, "testPath", "Wrong storageDirectory", ); assert.deepStrictEqual( - config.instrumentationOptions.azureSdk?.enabled, + mergedConfig.instrumentationOptions.azureSdk?.enabled, true, "Wrong azureSdk", ); - assert.deepStrictEqual(config.instrumentationOptions.mongoDb?.enabled, true, "Wrong mongoDb"); - assert.deepStrictEqual(config.instrumentationOptions.mySql?.enabled, true, "Wrong mySql"); assert.deepStrictEqual( - config.instrumentationOptions.postgreSql?.enabled, + mergedConfig.instrumentationOptions.mongoDb?.enabled, + true, + "Wrong mongoDb", + ); + assert.deepStrictEqual( + mergedConfig.instrumentationOptions.mySql?.enabled, + true, + "Wrong mySql", + ); + assert.deepStrictEqual( + mergedConfig.instrumentationOptions.postgreSql?.enabled, true, "Wrong postgreSql", ); - assert.deepStrictEqual(config.instrumentationOptions.redis?.enabled, true, "Wrong redis"); - assert.deepStrictEqual(config.instrumentationOptions.redis4?.enabled, true, "Wrong redis4"); + assert.deepStrictEqual( + mergedConfig.instrumentationOptions.redis?.enabled, + true, + "Wrong redis", + ); + assert.deepStrictEqual( + mergedConfig.instrumentationOptions.redis4?.enabled, + true, + "Wrong redis4", + ); }); - it("JSON config values take precedence over others", () => { - const jsonOptions = { - azureMonitorExporterOptions: { - connectionString: "testConnString", - storageDirectory: "teststorageDirectory", - disableOfflineStorage: true, - }, - samplingRatio: 1, - tracesPerSecond: 2, - instrumentationOptions: { - http: { enabled: true }, - azureSdk: { enabled: true }, - mongoDb: { enabled: true }, - mySql: { enabled: true }, - postgreSql: { enabled: true }, - redis: { enabled: true }, - redis4: { enabled: true }, - }, - }; - vi.stubEnv("APPLICATIONINSIGHTS_CONFIGURATION_CONTENT", JSON.stringify(jsonOptions)); - + it("programmatic options take precedence over defaults", () => { const options: MicrosoftOpenTelemetryOptions = { - samplingRatio: 0.5, - tracesPerSecond: 4, + samplingRatio: 0.6, + tracesPerSecond: 4.5, instrumentationOptions: { http: { enabled: false }, azureSdk: { enabled: false }, @@ -123,48 +132,24 @@ describe("Library/Config", () => { }; const config = new InternalConfig(options); - assert.strictEqual(config.samplingRatio, jsonOptions.samplingRatio); - assert.strictEqual(config.tracesPerSecond, jsonOptions.tracesPerSecond); - assert.strictEqual( - config.instrumentationOptions?.http?.enabled, - jsonOptions.instrumentationOptions.http.enabled, - ); - assert.strictEqual( - config.instrumentationOptions?.azureSdk?.enabled, - jsonOptions.instrumentationOptions.azureSdk.enabled, - ); - assert.strictEqual( - config.instrumentationOptions?.mongoDb?.enabled, - jsonOptions.instrumentationOptions.mongoDb.enabled, - ); - assert.strictEqual( - config.instrumentationOptions?.mySql?.enabled, - jsonOptions.instrumentationOptions.mySql.enabled, - ); - assert.strictEqual( - config.instrumentationOptions?.postgreSql?.enabled, - jsonOptions.instrumentationOptions.postgreSql.enabled, - ); - assert.strictEqual( - config.instrumentationOptions?.redis?.enabled, - jsonOptions.instrumentationOptions.redis.enabled, - ); - assert.strictEqual( - config.instrumentationOptions?.redis4?.enabled, - jsonOptions.instrumentationOptions.redis4.enabled, - ); + assert.strictEqual(config.samplingRatio, 0.6); + assert.strictEqual(config.tracesPerSecond, 4.5); + assert.strictEqual(config.instrumentationOptions?.http?.enabled, false); + assert.strictEqual(config.instrumentationOptions?.azureSdk?.enabled, false); + assert.strictEqual(config.instrumentationOptions?.mongoDb?.enabled, false); + assert.strictEqual(config.instrumentationOptions?.mySql?.enabled, false); + assert.strictEqual(config.instrumentationOptions?.postgreSql?.enabled, false); + assert.strictEqual(config.instrumentationOptions?.redis?.enabled, false); + assert.strictEqual(config.instrumentationOptions?.redis4?.enabled, false); assert.strictEqual( config.azureMonitorExporterOptions?.connectionString, - jsonOptions.azureMonitorExporterOptions.connectionString, + "testConnStringOther", ); assert.strictEqual( config.azureMonitorExporterOptions?.storageDirectory, - jsonOptions.azureMonitorExporterOptions.storageDirectory, - ); - assert.strictEqual( - config.azureMonitorExporterOptions?.disableOfflineStorage, - jsonOptions.azureMonitorExporterOptions.disableOfflineStorage, + "teststorageDirectoryOther", ); + assert.strictEqual(config.azureMonitorExporterOptions?.disableOfflineStorage, false); }); it("Default config", () => { @@ -207,27 +192,16 @@ describe("Library/Config", () => { }); it("Partial configurations are supported", () => { - const env = <{ [id: string]: string }>{}; - - const jsonOptions = { - azureMonitorExporterOptions: { - storageDirectory: "teststorageDirectory", - }, - samplingRatio: 0.7, - instrumentationOptions: { - redis4: { enabled: false }, - }, - }; - env["APPLICATIONINSIGHTS_CONFIGURATION_CONTENT"] = JSON.stringify(jsonOptions); - process.env = env; - const options: MicrosoftOpenTelemetryOptions = { + samplingRatio: 0.7, instrumentationOptions: { http: { enabled: false }, + redis4: { enabled: false }, }, azureMonitor: { azureMonitorExporterOptions: { connectionString: "testConnectionString", + storageDirectory: "teststorageDirectory", }, }, }; @@ -284,13 +258,18 @@ describe("Library/Config", () => { assert.strictEqual(config.samplingRatio, 0); }); - it("instrumentation key validation-valid key passed", () => { - new InternalConfig({ - azureMonitorExporterOptions: { - connectionString: "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333", + it("should merge azure monitor connection string from options", () => { + const config = new InternalConfig({ + azureMonitor: { + azureMonitorExporterOptions: { + connectionString: "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333", + }, }, }); - expect(warnStub).not.toHaveBeenCalled(); + assert.strictEqual( + config.azureMonitorExporterOptions.connectionString, + "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333", + ); }); // TODO: these tests are incorrect on main @@ -299,8 +278,10 @@ describe("Library/Config", () => { // When changed to `assert.ok(warnStub.calledOnce) it fails on main it.todo("instrumentation key validation-invalid key passed", () => { new InternalConfig({ - azureMonitorExporterOptions: { - connectionString: "InstrumentationKey=1aa11111bbbb1ccc8dddeeeeffff3333", + azureMonitor: { + azureMonitorExporterOptions: { + connectionString: "InstrumentationKey=1aa11111bbbb1ccc8dddeeeeffff3333", + }, }, }); expect(warnStub).toHaveBeenCalled(); @@ -308,8 +289,10 @@ describe("Library/Config", () => { it.todo("instrumentation key validation-invalid key passed", () => { new InternalConfig({ - azureMonitorExporterOptions: { - connectionString: "abc", + azureMonitor: { + azureMonitorExporterOptions: { + connectionString: "abc", + }, }, }); expect(warnStub).toHaveBeenCalled(); diff --git a/test/internal/unit/shared/jsonConfig.test.ts b/test/internal/unit/shared/jsonConfig.test.ts deleted file mode 100644 index 99236617..00000000 --- a/test/internal/unit/shared/jsonConfig.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import path from "node:path"; -import { JsonConfig } from "../../../../src/shared/jsonConfig.js"; -import { afterAll, afterEach, assert, beforeEach, describe, it, vi } from "vitest"; - -describe("Json Config", () => { - let originalEnv: NodeJS.ProcessEnv; - - beforeEach(() => { - originalEnv = process.env; - (JsonConfig["_instance"] as any) = undefined; - }); - - afterEach(() => { - process.env = originalEnv; - vi.restoreAllMocks(); - }); - - afterAll(() => { - (JsonConfig["_instance"] as any) = undefined; - }); - - describe("config path", () => { - it("Default file path", () => { - const config = JsonConfig.getInstance(); - const defaultPath = path.join(process.cwd(), "applicationinsights.json"); - assert.deepStrictEqual(config["_tempDir"], defaultPath); - }); - - it("Absolute file path", () => { - const env = <{ [id: string]: string }>{}; - const customConfigJSONPath = path.resolve(__dirname, "config.json"); - env["APPLICATIONINSIGHTS_CONFIGURATION_FILE"] = customConfigJSONPath; - process.env = env; - const config = JsonConfig.getInstance(); - assert.deepStrictEqual( - config.azureMonitor?.azureMonitorExporterOptions?.connectionString, - "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/", - ); - }); - - it("Relative file path", () => { - const env = <{ [id: string]: string }>{}; - const customConfigJSONPath = "test/internal/unit/shared/config.json"; - env["APPLICATIONINSIGHTS_CONFIGURATION_FILE"] = customConfigJSONPath; - process.env = env; - const config = JsonConfig.getInstance(); - assert.deepStrictEqual( - config.azureMonitor?.azureMonitorExporterOptions?.connectionString, - "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/", - ); - }); - }); - - describe("configuration values", () => { - it("Should take configurations from JSON config file", () => { - const env = <{ [id: string]: string }>{}; - const customConfigJSONPath = path.resolve(__dirname, "config.json"); - env["APPLICATIONINSIGHTS_CONFIGURATION_FILE"] = customConfigJSONPath; - process.env = env; - const config = JsonConfig.getInstance(); - assert.deepStrictEqual( - config.azureMonitor?.azureMonitorExporterOptions?.connectionString, - "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/", - ); - assert.deepStrictEqual( - config.azureMonitor?.azureMonitorExporterOptions?.disableOfflineStorage, - true, - ); - assert.deepStrictEqual( - config.azureMonitor?.azureMonitorExporterOptions?.storageDirectory, - "testPath", - ); - assert.deepStrictEqual(config.samplingRatio, 0.3, "Wrong samplingRatio"); - assert.deepStrictEqual(config.tracesPerSecond, 0.2, "Wrong tracesPerSecond"); - assert.deepStrictEqual( - config.instrumentationOptions?.azureSdk?.enabled, - true, - "Wrong azureSdk", - ); - assert.deepStrictEqual( - config.instrumentationOptions?.mongoDb?.enabled, - true, - "Wrong mongoDb", - ); - assert.deepStrictEqual(config.instrumentationOptions?.mySql?.enabled, true, "Wrong mySql"); - assert.deepStrictEqual( - config.instrumentationOptions?.postgreSql?.enabled, - true, - "Wrong postgreSql", - ); - assert.deepStrictEqual(config.instrumentationOptions?.redis?.enabled, true, "Wrong redis"); - assert.deepStrictEqual(config.instrumentationOptions?.redis4?.enabled, true, "Wrong redis4"); - }); - - it("Should take configurations from JSON config file over environment variables if both are configured", () => { - const env = <{ [id: string]: string }>{}; - const customConfigJSONPath = path.resolve(__dirname, "config.json"); - env["APPLICATIONINSIGHTS_CONFIGURATION_FILE"] = customConfigJSONPath; - env["APPLICATIONINSIGHTS_CONNECTION_STRING"] = "TestConnectionString"; - process.env = env; - const config = JsonConfig.getInstance(); - assert.deepStrictEqual( - config.azureMonitor?.azureMonitorExporterOptions?.connectionString, - "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://centralus-0.in.applicationinsights.azure.com/", - ); - }); - - it("JSON config through env variable", () => { - const env = <{ [id: string]: string }>{}; - - const inputJson = { - azureMonitorExporterOptions: { - connectionString: "testConnString", - storageDirectory: "teststorageDirectory", - disableOfflineStorage: true, - }, - samplingRatio: 1, - tracesPerSecond: 0.8, - instrumentationOptions: { - http: { enabled: true }, - azureSdk: { enabled: false }, - mongoDb: { enabled: false }, - mySql: { enabled: false }, - postgreSql: { enabled: false }, - redis: { enabled: false }, - redis4: { enabled: false }, - }, - }; - env["APPLICATIONINSIGHTS_CONFIGURATION_CONTENT"] = JSON.stringify(inputJson); - process.env = env; - const config = JsonConfig.getInstance(); - - assert.strictEqual(config.samplingRatio, 1); - assert.strictEqual(config.tracesPerSecond, 0.8); - assert.strictEqual(config.instrumentationOptions?.http?.enabled, true); - assert.strictEqual(config.instrumentationOptions?.azureSdk?.enabled, false); - assert.strictEqual(config.instrumentationOptions?.mongoDb?.enabled, false); - assert.strictEqual(config.instrumentationOptions?.mySql?.enabled, false); - assert.strictEqual(config.instrumentationOptions?.postgreSql?.enabled, false); - assert.strictEqual(config.instrumentationOptions?.redis?.enabled, false); - assert.strictEqual(config.instrumentationOptions?.redis4?.enabled, false); - assert.strictEqual( - config.azureMonitor?.azureMonitorExporterOptions?.connectionString, - "testConnString", - ); - assert.strictEqual( - config.azureMonitor?.azureMonitorExporterOptions?.storageDirectory, - "teststorageDirectory", - ); - assert.strictEqual( - config.azureMonitor?.azureMonitorExporterOptions?.disableOfflineStorage, - true, - ); - }); - }); -}); diff --git a/test/snippets.spec.ts b/test/snippets.spec.ts index 26c8f8ef..dfa846e9 100644 --- a/test/snippets.spec.ts +++ b/test/snippets.spec.ts @@ -89,12 +89,6 @@ describe("snippets", () => { useMicrosoftOpenTelemetry(options); }); - it("ReadmeSampleCustomConfig", () => { - process.env["APPLICATIONINSIGHTS_CONFIGURATION_FILE"] = "path/to/customConfig.json"; - // @ts-preserve-whitespace - // Application Insights SDK setup.... - }); - it("ReadmeSampleCustomInstrumentation", () => { useMicrosoftOpenTelemetry(); registerInstrumentations({ From 2105220ad1716a531a0b1142c2584afdb3c20d67 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:21:33 -0700 Subject: [PATCH 8/8] fix: resolve build errors, formatting issues for CI --- samples/src/a365Export.ts | 6 +-- samples/src/a365HostingMiddleware.ts | 29 ++++++++++--- samples/src/a365ManualScopes.ts | 46 +++++++++------------ src/a365/configuration/A365Configuration.ts | 2 + src/distro/distro.ts | 7 +++- 5 files changed, 53 insertions(+), 37 deletions(-) diff --git a/samples/src/a365Export.ts b/samples/src/a365Export.ts index fa948d27..ac74728f 100644 --- a/samples/src/a365Export.ts +++ b/samples/src/a365Export.ts @@ -83,9 +83,9 @@ async function main(): Promise { // A365 observability export configuration a365: { - enabled: true, // turn on the Agent365 exporter - tokenResolver: myTokenResolver, // called per-export with (agentId, tenantId) - clusterCategory: "dev", // target cluster: dev | test | preprod | prod | … + enabled: true, // turn on the Agent365 exporter + tokenResolver: myTokenResolver, // called per-export with (agentId, tenantId) + clusterCategory: "dev", // target cluster: dev | test | preprod | prod | … }, }); diff --git a/samples/src/a365HostingMiddleware.ts b/samples/src/a365HostingMiddleware.ts index b1e5ca06..41e4497c 100644 --- a/samples/src/a365HostingMiddleware.ts +++ b/samples/src/a365HostingMiddleware.ts @@ -85,7 +85,13 @@ function createMockTurnContext(): TurnContextLike { }; const turnState = new Map(); - const sendHandlers: Array<(ctx: TurnContextLike, activities: ActivityLike[], next: () => Promise) => Promise> = []; + const sendHandlers: Array< + ( + ctx: TurnContextLike, + activities: ActivityLike[], + next: () => Promise, + ) => Promise + > = []; return { activity, @@ -104,7 +110,8 @@ function createMockTurnContext(): TurnContextLike { let chain = sendNext; for (const h of [...sendHandlers].reverse()) { const prev = chain; - chain = () => h(this as unknown as TurnContextLike, outgoing, prev as () => Promise); + chain = () => + h(this as unknown as TurnContextLike, outgoing, prev as () => Promise); } await chain(); }, @@ -178,7 +185,9 @@ async function demoOutputLoggingMiddleware(): Promise { console.log("\n=== Demo 3: OutputLoggingMiddleware ===\n"); const middleware = new OutputLoggingMiddleware(); - const ctx = createMockTurnContext() as TurnContextLike & { sendActivity(text: string): Promise }; + const ctx = createMockTurnContext() as TurnContextLike & { + sendActivity(text: string): Promise; + }; // Set the auth token so the middleware can derive agent details ctx.turnState.set(A365_AUTH_TOKEN_KEY, ""); @@ -242,7 +251,9 @@ async function demoScopeUtils(): Promise { ctx, authToken, ); - console.log(` InferenceScope created from TurnContext (traceId: ${inferenceScope.getSpanContext().traceId})`); + console.log( + ` InferenceScope created from TurnContext (traceId: ${inferenceScope.getSpanContext().traceId})`, + ); console.log(" Input messages from activity.text were auto-recorded."); // Simulate response @@ -262,7 +273,9 @@ async function demoScopeUtils(): Promise { async function demoFullAgentTurn(): Promise { console.log("\n=== Demo 5: Full Agent Turn with Middleware ===\n"); - const ctx = createMockTurnContext() as TurnContextLike & { sendActivity(text: string): Promise }; + const ctx = createMockTurnContext() as TurnContextLike & { + sendActivity(text: string): Promise; + }; const authToken = ""; // Register middleware @@ -297,7 +310,11 @@ async function demoFullAgentTurn(): Promise { // LLM inference const inference = ScopeUtils.populateInferenceScopeFromTurnContext( - { operationName: InferenceOperationType.CHAT, model: "gpt-4o", providerName: "azure-openai" }, + { + operationName: InferenceOperationType.CHAT, + model: "gpt-4o", + providerName: "azure-openai", + }, ctx, authToken, ); diff --git a/samples/src/a365ManualScopes.ts b/samples/src/a365ManualScopes.ts index f18809d2..9f84ca07 100644 --- a/samples/src/a365ManualScopes.ts +++ b/samples/src/a365ManualScopes.ts @@ -129,10 +129,7 @@ async function callLLM( const scope = InferenceScope.start(request, details, agentDetails); try { // Record what we sent to the LLM - scope.recordInputMessages([ - "You are a helpful weather assistant.", - request.content as string, - ]); + scope.recordInputMessages(["You are a helpful weather assistant.", request.content as string]); // Simulate LLM response latency await new Promise((r) => setTimeout(r, 50)); @@ -212,10 +209,7 @@ async function executeTool( * - Records the tool result as input, the natural-language answer as output * - Records token counts and a "stop" finish reason */ -async function formatResponse( - request: A365Request, - toolResult: string, -): Promise { +async function formatResponse(request: A365Request, toolResult: string): Promise { const details: InferenceDetails = { operationName: InferenceOperationType.CHAT, model: "gpt-4o", @@ -248,11 +242,7 @@ async function formatResponse( /** Record the final streamed output with `OutputScope`. */ function recordOutput(request: A365Request, answer: string): void { - const scope = OutputScope.start( - request, - { messages: [answer] }, - agentDetails, - ); + const scope = OutputScope.start(request, { messages: [answer] }, agentDetails); scope.dispose(); } @@ -284,7 +274,12 @@ function demonstrateContextPropagation(): void { const scope = InvokeAgentScope.start( { conversationId: "cross-service-conv" }, { endpoint: { host: "service-b.internal", port: 8080 } }, - { ...agentDetails, agentId: "downstream-agent", agentName: "DownstreamBot", tenantId: "contoso-tenant-id" }, + { + ...agentDetails, + agentId: "downstream-agent", + agentName: "DownstreamBot", + tenantId: "contoso-tenant-id", + }, ); console.log(" Created child span in Service B, traceId:", scope.getSpanContext().traceId); scope.recordResponse("Handled by downstream agent"); @@ -324,19 +319,14 @@ async function main(): Promise { console.log("=== A365 Manual Telemetry Scopes Demo ===\n"); // 1️⃣ InvokeAgentScope — wraps the entire agent invocation - const invokeScope = InvokeAgentScope.start( - request, - {}, - agentDetails, - { - userDetails: { - userId: "user-jane-doe", - userName: "Jane Doe", - userEmail: "jane@contoso.com", - tenantId: "contoso-tenant-id", - }, + const invokeScope = InvokeAgentScope.start(request, {}, agentDetails, { + userDetails: { + userId: "user-jane-doe", + userName: "Jane Doe", + userEmail: "jane@contoso.com", + tenantId: "contoso-tenant-id", }, - ); + }); try { console.log("1. InvokeAgentScope started"); @@ -345,7 +335,9 @@ async function main(): Promise { // 2️⃣ InferenceScope — first LLM call (decides to use a tool) console.log("2. Calling LLM (InferenceScope)..."); const toolCall = await callLLM(request, invokeScope); - console.log(` LLM wants to call tool: ${toolCall.toolName}(${JSON.stringify(toolCall.args)})`); + console.log( + ` LLM wants to call tool: ${toolCall.toolName}(${JSON.stringify(toolCall.args)})`, + ); // 3️⃣ ExecuteToolScope — run the tool console.log("3. Executing tool (ExecuteToolScope)..."); diff --git a/src/a365/configuration/A365Configuration.ts b/src/a365/configuration/A365Configuration.ts index 4431da4a..3ad2afab 100644 --- a/src/a365/configuration/A365Configuration.ts +++ b/src/a365/configuration/A365Configuration.ts @@ -146,6 +146,8 @@ export class A365Configuration { this.hosting = { enabled: options?.hosting?.enabled ?? false, + adapter: options?.hosting?.adapter, + enableOutputLogging: options?.hosting?.enableOutputLogging ?? true, }; // Warn when A365-scoped options are set but A365 is not enabled diff --git a/src/distro/distro.ts b/src/distro/distro.ts index 55d168eb..06e7c227 100644 --- a/src/distro/distro.ts +++ b/src/distro/distro.ts @@ -30,7 +30,12 @@ import { } from "../azureMonitor/index.js"; import { isOtlpEnabled, createOtlpComponents } from "../otlp/index.js"; import { A365Configuration, Agent365Exporter, A365SpanProcessor } from "../a365/index.js"; -import type { MicrosoftOpenTelemetryOptions } from "../types.js"; +import type { + MicrosoftOpenTelemetryOptions, + InstrumentationOptions, + OpenAIAgentsInstrumentationConfig, + LangChainInstrumentationConfig, +} from "../types.js"; import { MICROSOFT_OPENTELEMETRY_VERSION } from "../types.js"; import { createInstrumentations, createSampler, createViews } from "./instrumentations.js"; import { Logger } from "../shared/logging/index.js";