Skip to main content
Apps and agents built on Microsoft Foundry (formerly Azure AI Foundry) are already OpenTelemetry-instrumented: the supported SDKs and agent frameworks emit spans following the OpenTelemetry GenAI semantic conventions — model calls, token usage, tool invocations, and multi-agent task hierarchies. Because that telemetry is standard OpenTelemetry, one initialisation block sends logs, metrics, and traces from your Foundry app straight to Bronto over OTLP/HTTP — no Bronto-specific SDK involved. This page covers the app-level signal. For general Azure platform, diagnostic, and activity logs from the same subscription, see Ingesting Azure Data into Bronto; for the attributes Bronto surfaces and how to search them, see LLM Observability.
GenAI instrumentation moves quickly. The package names, environment variables, and default attribute shapes below reflect the instrumentation available at the time of writing — confirm them against the release you deploy, and inspect a real trace before building version-sensitive dashboards.

Prerequisites

  • A Microsoft Foundry project with an instrumented app or agent
  • Python 3.10 or later, and pip
  • A Bronto ingestion API key — see API Keys for how to create one with ingestion permissions
  • Your Bronto region: eu or us
  • A dataset and collection name for the app’s telemetry. With OTLP these come from resource attributes, not headers: service.name → dataset, service.namespace → collection. See Data Organization.

Choose an OpenTelemetry-native instrumentation

Every framework in the Foundry ecosystem has more than one way to produce GenAI telemetry: vendor distributions such as OpenLLMetry or OpenInference wrap the framework in their own SDK, while the OpenTelemetry-native route uses either upstream OpenTelemetry instrumentation or the telemetry the framework emits itself. This page assumes the OpenTelemetry-native route. It keeps gen_ai.* attributes aligned with the upstream semantic conventions, puts no vendor SDK in the request path, and means the exporter configuration below is the only Bronto-specific code you write. Whichever you choose, everything below is identical — the exporter, the endpoints, the credential, the resource attributes — because all of these emit through the OpenTelemetry providers you configure.
Verify the exact package name, import path, and enablement call for your framework against its own documentation before copying this table into a runbook. Only the OpenAI SDK row is exercised by the code samples below.

Install the SDK and instrumentation

Swap the last package for the instrumentation your framework uses, or drop it entirely for a framework that emits OpenTelemetry itself.

Endpoints and authentication

Bronto has one OTLP/HTTP endpoint per signal, per region. Every request carries the API key in the x-bronto-api-key header. The SDK appends /v1/logs, /v1/metrics, and /v1/traces to OTEL_EXPORTER_OTLP_ENDPOINT automatically, so configure the base URL and let one setting cover all three signals:

Configure logs, metrics, and traces

Add this module to your project and call configure_bronto_telemetry() once, at the very top of your entrypoint — before the instrumented client is constructed and before your first log statement or span.
otel_bronto.py
The exporters take no arguments because they read the endpoint, protocol, and headers from the environment. To keep configuration in code instead, pass the full signal path to each one — OTLPSpanExporter(endpoint="https://ingestion.<REGION>.bronto.io/v1/traces", headers={"x-bronto-api-key": ...}), and likewise for logs and metrics. Enable instrumentation after the providers are registered, and build your client after that:
app.py
Any log emitted inside an active span automatically carries that span’s trace_id and span_id, so you can jump from a log line to the model call that produced it with no manual context propagation.
Short-lived processes. The batch processors and the metric reader export on a timer, so a script, job, or evaluation run that exits immediately can drop its last batch. Shut the providers down before exit:
Framework spans without code changes. Install the opentelemetry-instrumentation-<framework> packages for your web framework and HTTP client and run your app with opentelemetry-instrument python app.py. The CLI configures the providers itself from the same OTEL_* variables above, so use it instead of otel_bronto.py — and add opentelemetry-instrumentation-logging so standard-library logs are still bridged. See Python zero-code instrumentation.

Capturing prompts and responses

Two environment variables control whether the text of prompts and completions is captured, and in what shape:
  • OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT turns content capture on. It is off by default in the official instrumentations. span_only writes content to gen_ai.input.messages and gen_ai.output.messages as span attributes, which Bronto surfaces as searchable trace fields. The legacy true setting emits content as separate log records correlated by trace_id, where it is not queryable on the span itself — prefer span_only where your instrumentation supports it. Either way the content reaches Bronto, because the setup above exports logs as well as traces.
  • OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental opts into the current GenAI convention shape. Instrumentation libraries keep emitting an older schema by default so they don’t break existing dashboards, and support for this opt-in is instrumentation-specific — some libraries already emit the current fields, some need a different setting, some ignore it entirely.
Exercise caution when capturing prompts and responses — depending on the nature of your application they may include sensitive data.
Semantic Kernel and Microsoft Agent Framework do not use OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT — each has its own switch for sensitive-data capture. See their observability documentation, linked in the table above, for the exact variable names.

What you’ll see in Bronto

Traces

One app or agent invocation becomes one trace with nested spans: The attributes worth knowing on the model spans: Open Explore Traces and filter by the service.name you set. Open the newest trace and confirm the nesting above: the invocation at the root, a chat span per model call, and tool-call spans beneath it.

Metrics

Where your instrumentation implements the GenAI metric conventions, the meter provider exports aggregate instruments alongside the spans: Open Metric Explorer and select a recently emitted metric to confirm it carries the same service.name. Coverage of these instruments varies by library and version — spans are the reliable source of token counts, metrics the cheaper source of aggregate latency. See Explore Metrics for building views.

Logs

Application logs from the bridged logging module arrive with trace_id and span_id attached, so they line up with the trace that produced them. Log structured GenAI fields with extra= and they become queryable attributes:
Avoid printf-style logging (log.info("tokens=%s", n)) for data you want to query — that produces a flat message string rather than structured fields.

Search and aggregate

Bronto indexes attributes with a $ prefix, so query them in Log Search as field predicates:
Token usage is numeric, so it aggregates: Sum of $gen_ai.usage.output_tokens grouped by $gen_ai.request.model gives per-model consumption; the P95 of the same field finds unusually large responses. See LLM Observability for the full set of aggregations and Visualizations for building them into a dashboard.

Troubleshooting

Node.js and TypeScript

The pattern is the same: build the three providers with the OTLP/HTTP exporters from @opentelemetry/exporter-trace-otlp-proto, @opentelemetry/exporter-metrics-otlp-proto, and @opentelemetry/exporter-logs-otlp-proto, pointed at the same base endpoint with the same x-bronto-api-key header, then register your framework’s instrumentation. See Send Node.js logs, metrics, and traces to Bronto for the full setup.

References


For assistance or questions, contact support@bronto.io.