OpenTelemetry
Export agent, model, tool, task and shell activity as OpenTelemetry spans, so an agent run appears in the tracing you already have.
Fabric provides flat event exporters and a hierarchical observer. Your application owns the OpenTelemetry SDK, exporter, sampling, credentials, and shutdown lifecycle.
fh add opentelemetryThe managed recipe installs the OpenTelemetry API and creates a hierarchical observer configured with content redaction and Foundry-compatible semantic conventions.
import { trace } from '@opentelemetry/api';
import { init } from '@fabric-harness/sdk';
import { createOpenTelemetryObserver } from '@fabric-harness/sdk/otel-observer';
const observe = createOpenTelemetryObserver({
tracer: trace.getTracer('support-agent'),
conventions: 'foundry',
correlation: { agentId: 'support', jobId: runId, submissionId, tenantId },
});
const fabric = await init({ onEvent: observe });The observer creates nested turn, tool, shell, and task spans. conventions: 'foundry' emits gen_ai.* names and attributes compatible with Azure AI Foundry and OpenTelemetry GenAI consumers. Stable job, agent, session, submission, and tenant IDs are span attributes. Use openTelemetryExporter() when one flat span per duration-bearing Fabric event is preferable.
Do not export prompts, tool arguments, or results without a deliberate redaction and retention policy. See Telemetry for exporters, attributes, and event coverage.
Bounded, opt-in content
The hierarchical observer accepts contentBudgetBytes (default 57,344; safe integer
at least 128). Content remains off unless captureContent: true is set. This is a
shared per-span UTF-8 budget for content attribute keys plus values, not a separate
limit per field and not a total-span or collector limit. Operational metadata and
usage attributes remain available after content runs out. Up to 16,384 bytes (at most
half a small budget) are reserved for response-side content.
const observe = createOpenTelemetryObserver({
tracer: trace.getTracer('development-agent'),
captureContent: true, // Only after the application's disclosure/retention review.
contentBudgetBytes: 8_192,
// Runs on detached data. Returning undefined drops content; throwing omits it.
transformContent: content => applicationRedactor(content),
additionalSecrets: runtimeSecrets,
});Prompt text/results use fabric.content.input / fabric.content.output; compaction
summaries use fabric.content.summary. When supplied by an event producer, tool
input and output/result use gen_ai.tool.call.arguments and
gen_ai.tool.call.result. Ordinary session tool events deliberately omit raw payloads:
enabling this observer does not recover content absent from the event stream. The
flat exporter remains metadata-only.
Strings truncate on Unicode boundaries. Oversized structured values become the valid
JSON string "[TRUNCATED]", rather than malformed JSON. If even the omission marker
cannot fit, no content attribute is written. The boolean fabric.content.truncated
and fabric.content.omitted flags report loss as operational metadata outside the
content pool. They add a small, fixed overhead to the exported span. A span's
first value for each content key wins; repeated events cannot replenish its budget.
Credential-pattern redaction runs after the detached transform; a failed transform
never falls back to raw content. A custom sanitize event callback remains
authoritative and receives detached input. Built-in credential redaction is not a
PII classifier. Financial/identity applications should keep capture disabled until
their own redactor and retention/disclosure policy are verified. Content transforms
are not invoked when capture is off.