Skip to content
Merged
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Release History

## [Unreleased]
## [0.1.0-alpha.5] - 2026-04-24

### Breaking Changes
- Remove Azure Functions auto-instrumentation support from this package. The `instrumentationOptions.azureFunctions` option is no longer available.
Expand Down
75 changes: 75 additions & 0 deletions MIGRATION_A365.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,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, authScopes?) => string \| Promise<string>` | — | Required when exporting to A365 |
| `clusterCategory` | `ClusterCategory` | `"prod"` | Same category values as Agent365-nodejs |
| `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` | 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

Environment variable names are **unchanged** from Agent365-nodejs:
Expand Down Expand Up @@ -167,6 +183,43 @@ useMicrosoftOpenTelemetry({
> `SpanProcessor` from `@opentelemetry/sdk-trace-base` (e.g. `BatchSpanProcessor`,
> `SimpleSpanProcessor`) or a custom implementation.

### 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:
Expand Down Expand Up @@ -246,6 +299,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:
Expand All @@ -267,4 +340,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
76 changes: 72 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 enabled by default.

Set `enabled: true` or `enabled: false` explicitly for predictable behavior.

| Key | Type | Default | Description |
|---|---|---|---|
Expand All @@ -84,8 +91,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` | enabled | OpenAI Agents SDK instrumentation (requires `@openai/agents`) |
| `langchain` | `LangChainInstrumentationConfig` | enabled | LangChain instrumentation (requires `@langchain/core`) |

#### Turn instrumentations on/off

```typescript
useMicrosoftOpenTelemetry({
instrumentationOptions: {
// Disable specific built-in instrumentations
http: { enabled: false },
redis: { enabled: false },

// 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`: 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

Expand All @@ -109,7 +175,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<string>` | — | Token resolver for A365 service authentication |
| `tokenResolver` | `(agentId, tenantId, authScopes?) => string \| Promise<string>` | — | 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 |
Expand All @@ -128,6 +194,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

Expand Down
6 changes: 3 additions & 3 deletions samples/src/a365Export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ async function main(): Promise<void> {

// 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 | …
},
});

Expand Down
29 changes: 23 additions & 6 deletions samples/src/a365HostingMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@ function createMockTurnContext(): TurnContextLike {
};

const turnState = new Map<string, unknown>();
const sendHandlers: Array<(ctx: TurnContextLike, activities: ActivityLike[], next: () => Promise<unknown[]>) => Promise<unknown[]>> = [];
const sendHandlers: Array<
(
ctx: TurnContextLike,
activities: ActivityLike[],
next: () => Promise<unknown[]>,
) => Promise<unknown[]>
> = [];

return {
activity,
Expand All @@ -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<unknown[]>);
chain = () =>
h(this as unknown as TurnContextLike, outgoing, prev as () => Promise<unknown[]>);
}
await chain();
},
Expand Down Expand Up @@ -178,7 +185,9 @@ async function demoOutputLoggingMiddleware(): Promise<void> {
console.log("\n=== Demo 3: OutputLoggingMiddleware ===\n");

const middleware = new OutputLoggingMiddleware();
const ctx = createMockTurnContext() as TurnContextLike & { sendActivity(text: string): Promise<void> };
const ctx = createMockTurnContext() as TurnContextLike & {
sendActivity(text: string): Promise<void>;
};

// Set the auth token so the middleware can derive agent details
ctx.turnState.set(A365_AUTH_TOKEN_KEY, "<mock-token>");
Expand Down Expand Up @@ -242,7 +251,9 @@ async function demoScopeUtils(): Promise<void> {
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
Expand All @@ -262,7 +273,9 @@ async function demoScopeUtils(): Promise<void> {
async function demoFullAgentTurn(): Promise<void> {
console.log("\n=== Demo 5: Full Agent Turn with Middleware ===\n");

const ctx = createMockTurnContext() as TurnContextLike & { sendActivity(text: string): Promise<void> };
const ctx = createMockTurnContext() as TurnContextLike & {
sendActivity(text: string): Promise<void>;
};
const authToken = "<mock-token>";

// Register middleware
Expand Down Expand Up @@ -297,7 +310,11 @@ async function demoFullAgentTurn(): Promise<void> {

// 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,
);
Expand Down
46 changes: 19 additions & 27 deletions samples/src/a365ManualScopes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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<string> {
async function formatResponse(request: A365Request, toolResult: string): Promise<string> {
const details: InferenceDetails = {
operationName: InferenceOperationType.CHAT,
model: "gpt-4o",
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -324,19 +319,14 @@ async function main(): Promise<void> {
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");
Expand All @@ -345,7 +335,9 @@ async function main(): Promise<void> {
// 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)...");
Expand Down
1 change: 1 addition & 0 deletions samples/src/langchainInstrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ async function main(): Promise<void> {
},
instrumentationOptions: {
langchain: {
enabled: true,
Comment thread
hectorhdzg marked this conversation as resolved.
isContentRecordingEnabled: true,
},
},
Expand Down
Loading
Loading