Edit

Instrument applications with Microsoft OpenTelemetry Distro

Use this article to understand built-in instrumentation, autoinstrumentation, baggage, middleware, and manual scopes in Microsoft OpenTelemetry Distro.

Built-in instrumentation

The Microsoft OpenTelemetry Distro combines standard OpenTelemetry pipelines with Microsoft-curated instrumentation. The Distro can collect application telemetry, infrastructure telemetry, and agent or generative AI telemetry depending on language and configuration.

Category What it covers
Signal pipelines Traces, metrics, and logs.
Resource detection Service, host, cloud, and Azure runtime context where supported.
Infrastructure instrumentation HTTP, ASP.NET Core, Azure SDK, database clients, and logging frameworks where supported.
Generative AI instrumentation OpenAI, Azure OpenAI, Semantic Kernel, LangChain, OpenAI Agents SDK, and Agent Framework where supported.
Manual agent scopes Agent invocation, tool execution, inference, and output telemetry where supported.
Exporters and processors Azure Monitor, Microsoft Agent 365, OTLP, console output, span processors, log processors, and metric readers.

Instrumentation coverage

Language Common application instrumentation Common agent and generative AI instrumentation
Python OpenTelemetry resources, processors, readers, logging, metrics, and traces. Semantic Kernel, OpenAI Agents SDK, Agent Framework, LangChain, Microsoft Agent 365 baggage, and Microsoft Agent 365 scopes.
Node.js HTTP, Azure SDK, Azure Functions, MongoDB, MySQL, PostgreSQL, Redis, Bunyan, and Winston. OpenAI Agents SDK, LangChain, Microsoft Agent 365 baggage, and Microsoft Agent 365 scopes.
.NET ASP.NET Core, HttpClient, SQL Client, Azure SDK, resource detection, metrics, and logs. Semantic Kernel, OpenAI and Azure OpenAI, Agent Framework, Microsoft Agent 365 baggage, and Microsoft Agent 365 scopes.

Automatic instrumentation listens to telemetry signals emitted by supported libraries and frameworks. Manual instrumentation is used when an application needs to describe agent-specific operations, such as invocation, tool execution, inference, or asynchronous output.

Add custom OpenTelemetry sources, meters, processors, or readers when your application emits telemetry that isn't covered by the built-in instrumentations.

Autoinstrumentation

Auto-instrumentation listens to telemetry emitted by supported frameworks and forwards it through the Distro's OpenTelemetry pipeline. For agent scenarios, set baggage such as tenant ID and agent ID before the instrumented framework creates spans.

Framework Python Node.js .NET
Semantic Kernel Supported Not supported Supported
OpenAI and OpenAI Agents SDK Supported Supported Supported
Agent Framework Supported Not supported Supported
LangChain Supported Supported Not listed

Semantic Kernel

from microsoft.opentelemetry import use_microsoft_opentelemetry

def token_resolver(agent_id, tenant_id):
    return "your-token"

use_microsoft_opentelemetry(
    enable_a365=True,
    a365_token_resolver=token_resolver,
    instrumentation_options={
        "semantic_kernel": {"enabled": True},
    },
)

OpenAI

from microsoft.opentelemetry import use_microsoft_opentelemetry

def token_resolver(agent_id, tenant_id):
    return "your-token"

use_microsoft_opentelemetry(
    enable_a365=True,
    a365_token_resolver=token_resolver,
    instrumentation_options={
        "openai_agents": {"enabled": True},
    },
)

Agent Framework

from microsoft.opentelemetry import use_microsoft_opentelemetry

def token_resolver(agent_id, tenant_id):
    return "your-token"

use_microsoft_opentelemetry(
    enable_a365=True,
    a365_token_resolver=token_resolver,
    instrumentation_options={
        "agent_framework": {"enabled": True},
    },
)

LangChain

from microsoft.opentelemetry import use_microsoft_opentelemetry

def token_resolver(agent_id, tenant_id):
    return "your-token"

use_microsoft_opentelemetry(
    enable_a365=True,
    a365_token_resolver=token_resolver,
    instrumentation_options={
        "langchain": {"enabled": True},
    },
)

Baggage

Baggage carries contextual attributes through the active OpenTelemetry context so spans created during a request can share identifiers such as tenant ID, agent ID, and conversation ID. Use baggage before starting auto-instrumented framework operations or manual scopes that need that context.

from microsoft.opentelemetry.a365.core import BaggageBuilder

baggage_scope = (
    BaggageBuilder()
    .tenant_id("tenant-123")
    .agent_id("agent-456")
    .conversation_id("conv-789")
    .build()
)

with baggage_scope:
    # Spans created in this context can receive these baggage values.
    pass

Baggage values should be set from the authoritative request context for the product surface. Avoid storing secrets or credentials in baggage.

Baggage middleware

Baggage middleware can populate context for incoming requests so application code doesn't need to build baggage manually for every activity. Use hosting middleware when your product surface provides the required tenant, agent, caller, channel, and conversation context.

Register middleware directly on the adapter.

from microsoft.opentelemetry.a365.hosting.middleware import BaggageMiddleware

adapter.use(BaggageMiddleware())

Alternatively, use the hosting manager when you need to configure hosting features together.

from microsoft.opentelemetry.a365.hosting.middleware import (
    ObservabilityHostingManager,
    ObservabilityHostingOptions,
)

manager = ObservabilityHostingManager()
manager.configure(adapter, ObservabilityHostingOptions(enable_baggage=True))

Middleware should avoid overwriting baggage that was already established by an originating request.

Manual instrumentation

Use manual instrumentation when automatic instrumentation doesn't describe the agent operation with enough detail. Manual scopes let an application describe common agent activities in a consistent way across languages.

Scope Use for
InvokeAgentScope The start and completion of an agent invocation.
ExecuteToolScope A tool call made by an agent.
InferenceScope An AI model inference operation.
OutputScope Output that must be recorded after the originating scope has already completed.

Reuse the same request and agent identity values across scopes in a request so related telemetry can be correlated.

Agent invocation

from microsoft.opentelemetry.a365.core import (
    AgentDetails,
    Channel,
    InvokeAgentScope,
    InvokeAgentScopeDetails,
    Request,
    ServiceEndpoint,
)

agent_details = AgentDetails(
    agent_id="agent-456",
    agent_name="Email Assistant",
    agent_description="An AI agent powered by Azure OpenAI",
    agentic_user_id="auid-123",
    agentic_user_email="agent@contoso.com",
    agent_blueprint_id="blueprint-789",
    tenant_id="tenant-123",
)

request = Request(
    content="Please help me organize my emails",
    session_id="session-42",
    conversation_id="conv-xyz",
    channel=Channel(name="msteams"),
)

scope_details = InvokeAgentScopeDetails(
    endpoint=ServiceEndpoint(hostname="myagent.contoso.com", port=443),
)

with InvokeAgentScope.start(
    request=request,
    scope_details=scope_details,
    agent_details=agent_details,
) as scope:
    scope.record_input_messages(["Please help me organize my emails"])

    # Run the agent invocation.

    invoke_scope.record_output_messages(["I found 15 urgent emails."])

Tool execution

from microsoft.opentelemetry.a365.core import (
    ExecuteToolScope,
    ServiceEndpoint,
    ToolCallDetails,
    ToolType,
)

tool_details = ToolCallDetails(
    tool_name="email-search",
    arguments={"query": "from:manager@contoso.com"},
    tool_call_id="tool-call-456",
    description="Search emails by criteria",
    tool_type=ToolType.FUNCTION.value,
    endpoint=ServiceEndpoint(
        hostname="tools.contoso.com",
        port=8080,
        protocol="https",
    ),
)

with ExecuteToolScope.start(
    request=request,
    details=tool_details,
    agent_details=agent_details,
) as scope:
    result = search_emails(tool_details.arguments)
    scope.record_response(result)

Inference

from microsoft.opentelemetry.a365.core import (
    InferenceCallDetails,
    InferenceOperationType,
    InferenceScope,
)

inference_details = InferenceCallDetails(
    operationName=InferenceOperationType.CHAT,
    model="gpt-4o-mini",
    providerName="azure-openai",
)

with InferenceScope.start(
    request=request,
    details=inference_details,
    agent_details=agent_details,
) as scope:
    scope.record_input_messages(["Summarize the following emails for me."])
    response = call_llm()
    scope.record_output_messages([response.text])
    scope.record_input_tokens(response.usage.input_tokens)
    scope.record_output_tokens(response.usage.output_tokens)
    scope.record_finish_reasons(["stop"])

Output

from microsoft.opentelemetry.a365.core import OutputScope, Response, SpanDetails

# Capture this before exiting the originating InvokeAgentScope context.
parent_context = invoke_scope.get_span_context()
response = Response(
    messages=["Here is your organized inbox."],
)

with OutputScope.start(
    request=request,
    response=response,
    agent_details=agent_details,
    user_details=None,
    span_details=SpanDetails(parent_context=parent_context),
) as scope:
    pass

Product documentation should define any product-specific validation requirements for these scopes.

Next steps