> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Telemetry API

> API reference for this.telemetry — custom spans, events, and attributes in execution contexts

The `this.telemetry` API is available on all execution contexts (`ToolContext`, `ResourceContext`, `PromptContext`, `AgentContext`) when observability is enabled. It provides a simple interface for creating custom spans, recording events, and setting attributes — with automatic trace context propagation.

<Info>
  Requires `@frontmcp/observability` installed and `observability` config enabled. See the [Observability Guide](/frontmcp/guides/observability) for setup.
</Info>

***

## TelemetryAccessor

Available as `this.telemetry` in all execution contexts. One instance per request (context-scoped). Automatically inherits the current request's trace ID, session ID, and scope.

### `startSpan(name, attributes?)`

Create a child span under the current execution span.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
startSpan(name: string, attributes?: Record<string, string | number | boolean>): TelemetrySpan
```

You **must** call `span.end()` or `span.endWithError()` when done.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const span = this.telemetry.startSpan('fetch-user', { 'user.id': userId });
try {
  const user = await this.fetch(`/api/users/${userId}`);
  span.setAttribute('user.name', user.name);
  span.end();
} catch (err) {
  span.endWithError(err);
  throw err;
}
```

***

### `withSpan(name, fn, attributes?)`

Run a function within a span. The span is automatically ended on success or error.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
withSpan<T>(
  name: string,
  fn: (span: TelemetrySpan) => Promise<T>,
  attributes?: Record<string, string | number | boolean>,
): Promise<T>
```

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const data = await this.telemetry.withSpan('query-database', async (span) => {
  span.addEvent('query-sent');
  const results = await db.query(sql);
  span.setAttribute('rows', results.length);
  return results;
});
```

***

### `addEvent(name, attributes?)`

Add an event to the **active flow execution span** (e.g., the tool span during `execute()`).

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
addEvent(name: string, attributes?: Record<string, string | number | boolean>): void
```

Events are lightweight markers that appear on the parent span's timeline. Use them for milestones rather than creating child spans.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
this.telemetry.addEvent('cache-checked', { hit: false });
this.telemetry.addEvent('validation-complete', { fields: 5 });
```

<Tip>
  Events go on the **active execution span**, not a new span. If called during `tool.execute()`, they appear on the `"tool my_tool"` span. If called outside an execution context, a short-lived child span is created as a fallback.
</Tip>

***

### `setAttributes(attrs)`

Set attributes on the **active flow execution span**.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
setAttributes(attrs: Record<string, string | number | boolean>): void
```

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
this.telemetry.setAttributes({
  'user.tier': 'premium',
  'request.size': body.length,
  'cache.hit': true,
});
```

***

### `traceId`

Get the current request's trace ID. Useful for including in external API calls or logs.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
get traceId(): string
```

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const response = await this.fetch('/api/data', {
  headers: { 'X-Trace-Id': this.telemetry.traceId },
});
```

***

### `sessionId`

Get the privacy-safe session tracing ID (16-char SHA-256 hash).

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
get sessionId(): string
```

***

## TelemetrySpan

Returned by `startSpan()` and passed to `withSpan()` callbacks. All setter methods return `this` for chaining.

### Methods

| Method | Signature | Description |
| - | - | - |
| `setAttribute` | `(key: string, value: string \| number \| boolean) => this` | Set a single attribute |
| `setAttributes` | `(attrs: Record<string, string \| number \| boolean>) => this` | Set multiple attributes |
| `addEvent` | `(name: string, attributes?: Record<...>) => this` | Add a named event |
| `recordError` | `(error: Error) => this` | Record an exception on the span |
| `end` | `() => void` | End the span with OK status |
| `endWithError` | `(error: Error \| string) => void` | End with ERROR status |
| `raw` | `Span` (getter) | Access the underlying OTel Span |

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const span = this.telemetry.startSpan('process');
span
  .setAttribute('input.size', 1024)
  .addEvent('step-1-done')
  .addEvent('step-2-done', { items: 42 });

if (failed) {
  span.recordError(new Error('processing failed'));
}

span.end(); // Preserves ERROR status if recordError was called
```

***

## Counters (Metrics)

Counters are cumulative, monotonically-increasing metrics. Unlike spans (which describe one request) or events (which mark a point on a span), counters aggregate across many requests and are scraped by your monitoring backend at a steady cadence.

### `createCounter(name, description?)`

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
createCounter(name: string, description?: string): Counter

interface Counter {
  inc(by?: number, attrs?: Record<string, string | number | boolean>): void;
}
```

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const counter = this.telemetry.createCounter('my_app_widgets_total', 'Widgets processed');
counter.inc(1, { status: 'ok' });
```

### Built-in skill counters

When `skillsConfig.enabled: true` is set on `@FrontMcp`, the framework emits the following counters automatically:

| Counter | Attributes | Description |
| - | - | - |
| `frontmcp_skills_bundle_pulls_total` | `status: 'ok' \| 'error'`, `source: 'static' \| 'npm' \| 'saas-pull' \| 'filesystem' \| 'unknown'`, `reason` (errors only) | Skill bundle pulls / hot-swaps |
| `frontmcp_skills_signature_verifications_total` | `status: 'ok' \| 'error'` | Bundle signature verifications attempted |
| `frontmcp_skills_signature_failures_total` | `reason` (bounded vocabulary) | Signature failures, classified by reason |
| `frontmcp_skills_replay_checks_total` | `status: 'ok' \| 'error'` | Replay-protection checks |
| `frontmcp_skills_replay_rejects_total` | `reason` (bounded vocabulary) | Replay rejections, classified by reason |
| `frontmcp_skills_audit_dropped_total` | `reason` | Audit log records dropped (queue overflow / write failure) |

The framework also emits a `skill.bundle.swap` span (with `source`, `bundle_id`, `version`, `skill_count`, `from_version` attributes), and adds `skill_search.query`, `skill_search.results`, and `skill_action.phase` events to the active flow span when the skill HTTP catalog is exercised.

### Wiring a MeterProvider

Counters become observable once you register a global OTel `MeterProvider`. Without one, counters still increment in an in-memory snapshot (`getMetricSnapshot()` from `@frontmcp/observability`) intended for tests and local debugging only.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { metrics } from '@opentelemetry/api';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';

const meterProvider = new MeterProvider({
  resource: new Resource({ 'service.name': 'my-mcp-server' }),
  readers: [
    new PeriodicExportingMetricReader({
      exporter: new OTLPMetricExporter({
        url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://localhost:4318/v1/metrics',
      }),
      exportIntervalMillis: 10_000,
    }),
  ],
});

metrics.setGlobalMeterProvider(meterProvider);
```

***

## Testing Utilities

Import from `@frontmcp/observability`:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import {
  createTestTracer,
  getFinishedSpans,
  assertSpanExists,
  assertSpanAttribute,
  findSpan,
  findSpansByAttribute,
} from '@frontmcp/observability';
```

### `createTestTracer(name?)`

Create an isolated test tracer with in-memory span exporter. Does **not** register globally — safe for parallel tests.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
function createTestTracer(name?: string): {
  tracer: Tracer;
  exporter: InMemorySpanExporter;
  provider: BasicTracerProvider;
  cleanup: () => Promise<void>;
}
```

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const { tracer, exporter, cleanup } = createTestTracer();

afterEach(() => exporter.reset());
afterAll(() => cleanup());
```

***

### `assertSpanExists(spans, name)`

Assert that a span with the given name exists. Returns the span or throws.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
function assertSpanExists(spans: ReadableSpan[], name: string): ReadableSpan
```

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const spans = getFinishedSpans(exporter);
const toolSpan = assertSpanExists(spans, 'tool get_weather');
```

***

### `assertSpanAttribute(span, key, value)`

Assert a span has a specific attribute value.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
function assertSpanAttribute(span: ReadableSpan, key: string, value: string | number | boolean): void
```

***

### `findSpan(spans, name)` / `findSpansByAttribute(spans, key, value)`

Query helpers for finding spans in test assertions.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const dbSpan = findSpan(spans, 'database-query');
const toolSpans = findSpansByAttribute(spans, 'mcp.component.type', 'tool');
```

***

## Configuration Types

### ObservabilityOptionsInterface

The config object for `@FrontMcp({ observability: { ... } })`:

| Property | Type | Default | Description |
| - | - | - | - |
| `tracing` | `boolean \| TracingOptions` | `true` | Enable/configure OTel tracing |
| `logging` | `boolean \| LoggingOptions` | `false` | Enable/configure structured logging |
| `requestLogs` | `boolean \| RequestLogOptions` | `false` | Enable/configure per-request log collection |

### TracingOptions

| Property | Type | Default | Description |
| - | - | - | - |
| `httpSpans` | `boolean` | `true` | HTTP request spans |
| `executionSpans` | `boolean` | `true` | Tool/resource/prompt/agent spans |
| `fetchSpans` | `boolean` | `true` | Outbound `ctx.fetch()` spans |
| `flowStageEvents` | `boolean` | `true` | Flow stage events on execution spans |
| `hookSpans` | `boolean` | `false` | Individual hook spans (verbose) |
| `transportSpans` | `boolean` | `true` | SSE/HTTP transport spans |
| `authSpans` | `boolean` | `true` | Auth/session verify spans |
| `oauthSpans` | `boolean` | `true` | OAuth flow spans |
| `elicitationSpans` | `boolean` | `true` | Elicitation request/result spans |
| `startupReport` | `boolean` | `true` | Emit startup telemetry on first request |

### SinkConfig

| Type | Description | Platform |
| - | - | - |
| `{ type: 'stdout' }` | NDJSON to stdout (12-factor) | Node.js |
| `{ type: 'console' }` | `console.log/warn/error` | Browser, Node.js |
| `{ type: 'otlp', endpoint, headers? }` | OTLP HTTP to any backend | Node.js |
| `{ type: 'winston', logger }` | Forward to winston instance | Node.js |
| `{ type: 'pino', logger }` | Forward to pino instance | Node.js |
| `{ type: 'callback', fn }` | Custom callback function | Any |

***

## Related

<CardGroup cols={2}>
  <Card title="Observability Guide" icon="rocket" href="/frontmcp/guides/observability">
    Step-by-step setup with vendor integrations
  </Card>

  <Card title="Observability Feature" icon="activity" href="/frontmcp/features/observability">
    Overview of what FrontMCP observability provides
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.