Skill v1.0.0
Trusted Publisher100/100version: "1.0.0"
name: maf-tracing description: "Enable tracing and logging for Microsoft Agent Framework (MAF) workflows. Configures OpenTelemetry export to Azure Application Insights and/or a generic OTLP endpoint using environment variables. Adds required packages to requirements.txt. WHEN: enable tracing, add tracing, enable logging, add logging, configure telemetry, Application Insights for MAF, OTLP export, observe workflow, monitor agent workflow, trace agent framework, instrument MAF, add observability, trace workflow executions, debug workflow." license: MIT metadata: author: Team version: "1.0.0"
Enable Tracing & Logging for MAF Workflows
Configure OpenTelemetry-based tracing for Microsoft Agent Framework workflows, exporting to Azure Application Insights and/or a generic OTLP endpoint.
Triggers
Activate this skill when the user wants to:
- Enable tracing or logging for a MAF workflow
- Export telemetry to Azure Application Insights
- Export traces to an OTLP-compatible endpoint (Jaeger, Zipkin, Grafana Tempo, etc.)
- Add observability or monitoring to an
agent-frameworkproject - Debug or inspect executor/agent/LLM spans in a MAF workflow
Background
MAF automatically emits OpenTelemetry spans for every executor invocation, agent call, and LLM request. No instrumentation changes are needed inside Executor classes. You only need to:
- Configure an exporter — where spans are sent (Application Insights, OTLP endpoint, or both)
- Call `configure_otel_providers()` — activates MAF's built-in instrumentation
This must happen once at application startup, before any workflow.run() calls.
Rules
- Read the user's project first — Check for an existing
requirements.txt,.env, and entry-point script (e.g.,main.py,app.py,run_*.py). - Ask what export destination(s) are needed — Application Insights, OTLP endpoint, or both. If unclear, default to both.
- Do not modify Executor classes — MAF tracing is automatic. Never add manual
tracer.start_span()calls inside@handlermethods unless the user explicitly asks for custom spans. - Call order matters — Exporter configuration must happen BEFORE
configure_otel_providers(), and both must happen BEFORE anyworkflow.run(). - Add packages to `requirements.txt` — Append only the packages the user needs (see Packages section). Do not duplicate existing entries.
- Use environment variables — Never hardcode connection strings or endpoints. Always read from
os.environor.env. - Generate `.env.example` — Provide a template showing which environment variables are required.
- Python logging integration — When the user asks for logging (not just tracing), also configure the Python
loggingmodule to export via OpenTelemetry usingopentelemetry-sdklog handler.
Environment Variables
| Variable | Required For | Description | |
|---|---|---|---|
APPLICATIONINSIGHTS_CONNECTION_STRING | Application Insights | Connection string from Azure Portal → App Insights → Overview | |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP export | Base URL of the OTLP collector (e.g., http://localhost:4318) | |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT | OTLP export (traces only) | Overrides the base endpoint for trace signals only | |
OTEL_EXPORTER_OTLP_PROTOCOL | OTLP export | Protocol: http/protobuf (default) or grpc | |
OTEL_SERVICE_NAME | Optional | Service name shown in trace backends (defaults to Python process name) |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTtakes precedence overOTEL_EXPORTER_OTLP_ENDPOINTfor traces.
Packages
| Package | Version | When Needed | |
|---|---|---|---|
agent-framework | >=1.0.1 | Always (provides configure_otel_providers) | |
azure-monitor-opentelemetry | >=1.6.4 | Application Insights export | |
opentelemetry-exporter-otlp-proto-http | >=1.25.0 | OTLP/HTTP export | |
opentelemetry-exporter-otlp-proto-grpc | >=1.25.0 | OTLP/gRPC export (only if OTEL_EXPORTER_OTLP_PROTOCOL=grpc) | |
opentelemetry-sdk | >=1.25.0 | Custom spans or Python logging integration | |
python-dotenv | any | Loading .env files |
Setup Patterns
Pattern A: Application Insights Only
Use when the user wants to send traces to Azure Application Insights.
Required env var: APPLICATIONINSIGHTS_CONNECTION_STRING
Required packages:
azure-monitor-opentelemetry>=1.6.4
Setup code (add at the top of the entry-point script, before workflow.run()):
import osfrom dotenv import load_dotenvfrom azure.monitor.opentelemetry import configure_azure_monitorfrom agent_framework.observability import configure_otel_providersload_dotenv()# Step 1: Configure Azure Monitor exporter (traces, metrics, logs → App Insights)configure_azure_monitor(connection_string=os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"])# Step 2: Enable MAF's built-in instrumentation (executor, agent, LLM spans)configure_otel_providers()
Pattern B: OTLP Endpoint Only
Use when the user wants to send traces to a generic OTLP-compatible backend (Jaeger, Grafana Tempo, Aspire Dashboard, etc.).
Required env var: OTEL_EXPORTER_OTLP_ENDPOINT
Required packages:
opentelemetry-sdk>=1.25.0opentelemetry-exporter-otlp-proto-http>=1.25.0
Setup code:
import osfrom dotenv import load_dotenvfrom opentelemetry import tracefrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import BatchSpanProcessorfrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom agent_framework.observability import configure_otel_providersload_dotenv()# Step 1: Set up the OTLP exporter with a TracerProviderresource = Resource.create({"service.name": os.environ.get("OTEL_SERVICE_NAME", "maf-workflow"),})tracer_provider = TracerProvider(resource=resource)otlp_exporter = OTLPSpanExporter(endpoint=os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")or os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"),)tracer_provider.add_span_processor(BatchSpanProcessor(otlp_exporter))trace.set_tracer_provider(tracer_provider)# Step 2: Enable MAF's built-in instrumentationconfigure_otel_providers()
Pattern C: Both Application Insights and OTLP
Use when the user wants dual export — Application Insights for Azure-native monitoring plus an OTLP backend for local/third-party observability.
Required env vars: APPLICATIONINSIGHTS_CONNECTION_STRING, OTEL_EXPORTER_OTLP_ENDPOINT
Required packages:
azure-monitor-opentelemetry>=1.6.4opentelemetry-sdk>=1.25.0opentelemetry-exporter-otlp-proto-http>=1.25.0
Setup code:
import osfrom dotenv import load_dotenvfrom azure.monitor.opentelemetry import configure_azure_monitorfrom opentelemetry import tracefrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import BatchSpanProcessorfrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom agent_framework.observability import configure_otel_providersload_dotenv()# Step 1a: Configure Azure Monitor (sets up its own TracerProvider internally)configure_azure_monitor(connection_string=os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"])# Step 1b: Add OTLP exporter to the existing TracerProviderotlp_endpoint = (os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")or os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"))if otlp_endpoint:otlp_exporter = OTLPSpanExporter(endpoint=otlp_endpoint)tracer_provider: TracerProvider = trace.get_tracer_provider()tracer_provider.add_span_processor(BatchSpanProcessor(otlp_exporter))# Step 2: Enable MAF's built-in instrumentationconfigure_otel_providers()
Pattern D: Python Logging via OpenTelemetry
Use when the user also wants Python logging calls to be exported alongside traces.
Additional packages (on top of Pattern A, B, or C):
opentelemetry-sdk>=1.25.0
Setup code (add after the exporter setup, before configure_otel_providers()):
import loggingfrom opentelemetry.sdk._logs import LoggerProviderfrom opentelemetry.sdk._logs.export import BatchLogRecordProcessorfrom opentelemetry._logs import set_logger_provider# If using Application Insights, configure_azure_monitor() already handles log export.# If using OTLP only, set up the OTLP log exporter:from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporterlogger_provider = LoggerProvider(resource=resource)logger_provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter(endpoint=os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"),)))set_logger_provider(logger_provider)# Bridge Python logging to OpenTelemetryfrom opentelemetry.instrumentation.logging import LoggingInstrumentorLoggingInstrumentor().instrument(set_logging_format=True)
Note: When usingazure-monitor-opentelemetry(Pattern A/C),configure_azure_monitor()already captures Python logs by default. The above is only needed for OTLP-only setups.
Implementation Steps
Step 1: Determine export destination(s)
Ask the user or infer from context:
- Application Insights → Pattern A
- OTLP endpoint → Pattern B
- Both → Pattern C
If the user says "tracing" without specifying a destination, default to Pattern C (both).
Step 2: Update requirements.txt
Append the required packages. Do not duplicate existing entries. Example additions for Pattern C:
azure-monitor-opentelemetry>=1.6.4opentelemetry-sdk>=1.25.0opentelemetry-exporter-otlp-proto-http>=1.25.0
Step 3: Create or update .env.example
Add the environment variables relevant to the chosen pattern:
# === Tracing & Observability ===# Application Insights (Azure Portal → App Insights → Overview → Connection String)APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=xxx;IngestionEndpoint=https://xxx.in.applicationinsights.azure.com/# OTLP endpoint (e.g., Jaeger, Grafana Tempo, Aspire Dashboard)OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318# Optional: override service name in trace backendsOTEL_SERVICE_NAME=my-maf-workflow
Step 4: Add tracing setup to entry-point script
Insert the setup code from the appropriate pattern at the top of the entry-point script (after imports, before any workflow.run() call). The setup must execute once at module load / application startup.
Placement rules:
- If the entry point uses
asyncio.run(main()), place setup code insidemain()beforeworkflow.run(). - If the entry point is a module-level script, place setup code after imports and
load_dotenv(). - If the entry point is a web server (FastAPI, Flask), place setup code in the application factory or startup event.
Step 5: Verify
- Run the workflow and check that traces appear in the configured destination.
- For Application Insights: Azure Portal → Application Insights → Transaction Search → filter by "Dependency" or "Request".
- For OTLP: Check the collector backend UI (e.g., Jaeger UI at
http://localhost:16686).
Gotchas
- Call order matters —
configure_azure_monitor()and/or OTLP exporter setup must happen BEFOREconfigure_otel_providers(). If reversed, MAF spans won't be exported. - `configure_otel_providers()` must run BEFORE `workflow.run()` — Otherwise, executor-level spans are not generated.
- Do not call setup code inside Executors — Tracing setup is application-level, not per-request. Calling it inside a
@handlermethod will create duplicate exporters and corrupt traces. - `configure_azure_monitor()` creates its own TracerProvider — When combining with OTLP (Pattern C), add the OTLP exporter to the existing provider via
trace.get_tracer_provider()rather than creating a newTracerProvider. - Missing `configure_otel_providers()` — Without this call, you'll see Application Insights or OTLP infrastructure telemetry but no MAF-specific spans (executor transitions, agent calls, LLM requests).
- Connection string format — The
APPLICATIONINSIGHTS_CONNECTION_STRINGstarts withInstrumentationKey=followed by a GUID. Do not confuse it with the Instrumentation Key alone. - OTLP endpoint trailing path —
OTEL_EXPORTER_OTLP_ENDPOINTshould be the base URL (e.g.,http://localhost:4318). The SDK appends/v1/tracesautomatically. Do not include/v1/tracesin the env var. - gRPC vs HTTP — Default protocol is
http/protobuf(port 4318). If the collector uses gRPC (port 4317), setOTEL_EXPORTER_OTLP_PROTOCOL=grpcand installopentelemetry-exporter-otlp-proto-grpcinstead.
Example: Complete Entry Point with Tracing
"""Entry point for a MAF workflow with full tracing setup."""import asyncioimport osfrom dotenv import load_dotenvload_dotenv()def setup_tracing():"""Configure telemetry exporters and MAF instrumentation. Call once at startup."""from agent_framework.observability import configure_otel_providers# Application Insightsappinsights_conn = os.environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING")if appinsights_conn:from azure.monitor.opentelemetry import configure_azure_monitorconfigure_azure_monitor(connection_string=appinsights_conn)# OTLP endpoint (optional, additive)otlp_endpoint = (os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")or os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"))if otlp_endpoint:from opentelemetry import tracefrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import BatchSpanProcessorfrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterotlp_exporter = OTLPSpanExporter(endpoint=otlp_endpoint)tracer_provider = trace.get_tracer_provider()# If Azure Monitor already set a TracerProvider, reuse it; otherwise create oneif not isinstance(tracer_provider, TracerProvider):resource = Resource.create({"service.name": os.environ.get("OTEL_SERVICE_NAME", "maf-workflow"),})tracer_provider = TracerProvider(resource=resource)trace.set_tracer_provider(tracer_provider)tracer_provider.add_span_processor(BatchSpanProcessor(otlp_exporter))# Enable MAF's built-in spans (must be last)configure_otel_providers()async def main():setup_tracing()# Import and run your workflow herefrom workflow import create_workflowworkflow = create_workflow()result = await workflow.run("Hello, world!")print(result.get_outputs())if __name__ == "__main__":asyncio.run(main())