diff --git a/CHANGELOG.md b/CHANGELOG.md index f83fabf6..872e875d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **OutputLoggingMiddleware**: Middleware that creates OutputScope spans for outgoing messages with lazy parent span linking via `A365_PARENT_SPAN_KEY`. - **ObservabilityHostingManager**: Manager for configuring hosting-layer observability middleware with `ObservabilityHostingOptions`. +### Added +- **Agent365ExporterOptions**: New `httpRequestTimeoutMilliseconds` option (default 30s) controls the per-HTTP-request timeout for backend calls. This is distinct from `exporterTimeoutMilliseconds` which controls the overall BatchSpanProcessor export deadline. + +### Fixed +- **Agent365ExporterOptions**: `exporterTimeoutMilliseconds` default increased from 30s to 90s to allow sufficient time for retries across multiple identity groups within a single export cycle. + ### Changed +- **ObservabilityHostingManager**: `enableBaggage` option now defaults to `false` (was `true`). Callers must explicitly set `enableBaggage: true` to register the BaggageMiddleware. - `ScopeUtils.deriveAgentDetails` now resolves `agentId` via `activity.getAgenticInstanceId()` for embodied (agentic) agents only. For non-embodied agents, `agentId` is `undefined` since the token's app ID cannot reliably be attributed to the agent. - `ScopeUtils.deriveAgentDetails` now resolves `agentBlueprintId` from the JWT `xms_par_app_azp` claim via `RuntimeUtility.getAgentIdFromToken()` instead of reading `recipient.agenticAppBlueprintId`. - `ScopeUtils.deriveAgentDetails` now resolves `agentUPN` via `activity.getAgenticUser()` instead of `recipient.agenticUserId`. diff --git a/packages/agents-a365-observability-hosting/src/middleware/ObservabilityHostingManager.ts b/packages/agents-a365-observability-hosting/src/middleware/ObservabilityHostingManager.ts index a5f05803..5d9eeb32 100644 --- a/packages/agents-a365-observability-hosting/src/middleware/ObservabilityHostingManager.ts +++ b/packages/agents-a365-observability-hosting/src/middleware/ObservabilityHostingManager.ts @@ -10,7 +10,7 @@ import { OutputLoggingMiddleware } from './OutputLoggingMiddleware'; * Configuration options for the hosting observability layer. */ export interface ObservabilityHostingOptions { - /** Enable baggage propagation middleware. Defaults to true. */ + /** Enable baggage propagation middleware. Defaults to false. */ enableBaggage?: boolean; /** Enable output logging middleware for tracing outgoing messages. Defaults to false. */ @@ -42,7 +42,7 @@ export class ObservabilityHostingManager { return; } - const enableBaggage = options.enableBaggage !== false; + const enableBaggage = options.enableBaggage === true; const enableOutputLogging = options.enableOutputLogging === true; if (enableBaggage) { diff --git a/packages/agents-a365-observability/src/tracing/exporter/Agent365Exporter.ts b/packages/agents-a365-observability/src/tracing/exporter/Agent365Exporter.ts index 0839f07b..b5b8df75 100644 --- a/packages/agents-a365-observability/src/tracing/exporter/Agent365Exporter.ts +++ b/packages/agents-a365-observability/src/tracing/exporter/Agent365Exporter.ts @@ -24,7 +24,6 @@ import logger, { formatError } from '../../utils/logging'; import { Agent365ExporterOptions } from './Agent365ExporterOptions'; import { ExporterEventNames } from './ExporterEventNames'; -const DEFAULT_HTTP_TIMEOUT_SECONDS = 30000; // 30 seconds in ms const DEFAULT_MAX_RETRIES = 3; interface OTLPExportRequest { @@ -247,7 +246,7 @@ export class Agent365Exporter implements SpanExporter { method: 'POST', headers, body, - signal: AbortSignal.timeout(DEFAULT_HTTP_TIMEOUT_SECONDS) + signal: AbortSignal.timeout(this.options.httpRequestTimeoutMilliseconds) }); correlationId = response?.headers?.get('x-ms-correlation-id') || response?.headers?.get('x-correlation-id') || 'unknown'; diff --git a/packages/agents-a365-observability/src/tracing/exporter/Agent365ExporterOptions.ts b/packages/agents-a365-observability/src/tracing/exporter/Agent365ExporterOptions.ts index 37382bdc..45523e8c 100644 --- a/packages/agents-a365-observability/src/tracing/exporter/Agent365ExporterOptions.ts +++ b/packages/agents-a365-observability/src/tracing/exporter/Agent365ExporterOptions.ts @@ -24,7 +24,8 @@ export type TokenResolver = (agentId: string, tenantId: string) => string | null * @property {boolean} [useS2SEndpoint] When true, exporter will POST to the S2S path (/observabilityService/tenants/{tenantId}/agents/{agentId}/traces). * @property {number} maxQueueSize Maximum span queue size before drops occur (passed to BatchSpanProcessor). * @property {number} scheduledDelayMilliseconds Delay between automatic batch flush attempts. - * @property {number} exporterTimeoutMilliseconds Per-export timeout (abort if exceeded). + * @property {number} exporterTimeoutMilliseconds Maximum time (ms) the BatchSpanProcessor waits for the entire export() call to complete before giving up. Covers partitioning, token resolution, and all HTTP retries. + * @property {number} httpRequestTimeoutMilliseconds Timeout (ms) for each individual HTTP request to the observability backend. Applies per fetch() call inside the retry loop; each retry gets a fresh timeout. * @property {number} maxExportBatchSize Maximum number of spans per export batch. */ export class Agent365ExporterOptions { @@ -43,8 +44,11 @@ export class Agent365ExporterOptions { /** Delay (ms) between automatic batch flush attempts. */ public scheduledDelayMilliseconds: number = 5000; - /** Per-export timeout in milliseconds. */ - public exporterTimeoutMilliseconds: number = 30000; + /** Maximum time (ms) the BatchSpanProcessor waits for the entire export() call to complete. */ + public exporterTimeoutMilliseconds: number = 90000; + + /** Timeout (ms) for each individual HTTP request to the observability backend. Each retry attempt gets a fresh timeout. */ + public httpRequestTimeoutMilliseconds: number = 30000; /** Maximum number of spans per export batch. */ public maxExportBatchSize: number = 512; diff --git a/tests/observability/extension/hosting/observability-hosting-manager.test.ts b/tests/observability/extension/hosting/observability-hosting-manager.test.ts index 99eae6e3..7b2d5db7 100644 --- a/tests/observability/extension/hosting/observability-hosting-manager.test.ts +++ b/tests/observability/extension/hosting/observability-hosting-manager.test.ts @@ -12,24 +12,30 @@ function mockAdapter() { } describe('ObservabilityHostingManager', () => { - it('registers BaggageMiddleware by default', () => { + it('does not register BaggageMiddleware by default', () => { const adapter = mockAdapter(); new ObservabilityHostingManager().configure(adapter, {}); + expect(adapter.registered).toHaveLength(0); + }); + + it('registers BaggageMiddleware when enableBaggage is true', () => { + const adapter = mockAdapter(); + new ObservabilityHostingManager().configure(adapter, { enableBaggage: true }); expect(adapter.registered).toHaveLength(1); expect(adapter.registered[0]).toBeInstanceOf(BaggageMiddleware); }); - it('registers both middleware when enableOutputLogging is true', () => { + it('registers both middleware when enableBaggage and enableOutputLogging are true', () => { const adapter = mockAdapter(); - new ObservabilityHostingManager().configure(adapter, { enableOutputLogging: true }); + new ObservabilityHostingManager().configure(adapter, { enableBaggage: true, enableOutputLogging: true }); expect(adapter.registered).toHaveLength(2); expect(adapter.registered[0]).toBeInstanceOf(BaggageMiddleware); expect(adapter.registered[1]).toBeInstanceOf(OutputLoggingMiddleware); }); - it('skips BaggageMiddleware when enableBaggage is false', () => { + it('registers only OutputLoggingMiddleware when enableOutputLogging is true and enableBaggage is omitted', () => { const adapter = mockAdapter(); - new ObservabilityHostingManager().configure(adapter, { enableBaggage: false, enableOutputLogging: true }); + new ObservabilityHostingManager().configure(adapter, { enableOutputLogging: true }); expect(adapter.registered).toHaveLength(1); expect(adapter.registered[0]).toBeInstanceOf(OutputLoggingMiddleware); }); @@ -37,8 +43,8 @@ describe('ObservabilityHostingManager', () => { it('subsequent configure calls on same instance are no-ops', () => { const adapter = mockAdapter(); const manager = new ObservabilityHostingManager(); - manager.configure(adapter, { enableOutputLogging: true }); - manager.configure(adapter, { enableOutputLogging: true }); + manager.configure(adapter, { enableBaggage: true, enableOutputLogging: true }); + manager.configure(adapter, { enableBaggage: true, enableOutputLogging: true }); expect(adapter.registered).toHaveLength(2); }); });