Docs/telemetryv1.0.0-rc.1

Telemetry — Exporters

Overview

Exporters are the delivery layer: they receive telemetry data from the registry or tracing engine and forward it to a backend. UDIOT ships three exporters out of the box:

Exporter Data type Best for
OTLPMetricsExporter Metrics Experimental HTTP JSON delivery; validate the selected receiver
OTLPTraceExporter Traces Experimental HTTP JSON delivery; validate the selected receiver
ConsoleMetricsExporter Metrics Development and debugging

The built-in HTTP exporters catch network failures and log non-abort exceptions. Timeout aborts are silent. They do not check response.ok, retry delivery or report receiver rejection through their return value. Monitor ingestion and implement stronger delivery behavior in your own exporter where required.

Despite their names, these implementations are not proven standards-compliant OTLP encoders: metrics serialize framework snapshot fields directly, attributes are plain objects and trace status uses the framework's string value. The examples below describe the current wire payload, not backend certification.

Key Concepts

  • HTTP JSON delivery — defaults use port 4318 and resource/scope envelopes; receiver compatibility must be checked with the exact emitted payload.
  • AbortController timeout — both OTLP exporters use a 5000 ms (configurable) timeout with AbortController to prevent hung requests from blocking the flush loop.
  • Metric flush vs span export — metrics are batched and flushed periodically. Traces are exported span-by-span immediately after each span finalizes.

OTLPMetricsExporter

Sends the full MetricSnapshot to the configured /v1/metrics HTTP endpoint after each flush cycle.

Constructor

new OTLPMetricsExporter(options?: OTLPMetricsExporterOptions)

OTLPMetricsExporterOptions

Field Type Default Description
url string 'http://localhost:4318/v1/metrics' OTLP metrics endpoint
headers Record<string, string> {} Additional HTTP headers (e.g., auth tokens)
timeoutMs number 5000 Request timeout in milliseconds
fetcher typeof fetch global fetch Custom fetch implementation for testing

Wire format

The exporter wraps the snapshot in the OTLP resource metrics envelope:

{
  "resourceMetrics": [{
    "scopeMetrics": [{
      "scope": { "name": "@tagna/udiot" },
      "metrics": [
        {
          "name": "http.requests.total",
          "type": "counter",
          "sum": 1042,
          "count": 1042,
          "min": 1,
          "max": 1,
          "lastValue": 1,
          "updatedAt": 1711094400000,
          "attributes": { "method": "GET" }
        }
      ]
    }]
  }]
}

Example: configuring a metrics receiver

import {
  initializeTelemetry,
  OTLPMetricsExporter,
} from '@tagna/udiot/telemetry';

initializeTelemetry({
  metrics: {
    exporters: [
      new OTLPMetricsExporter({
        url: 'https://otel-collector.internal/v1/metrics',
        headers: {
          'Authorization': `Bearer ${process.env.OTEL_TOKEN}`,
        },
        timeoutMs: 8000,
      }),
    ],
    flushIntervalMs: 30_000,
  },
});

OTLPTraceExporter

Sends each completed TracingSpan to the configured /v1/traces HTTP endpoint immediately after the span finalizes.

Constructor

new OTLPTraceExporter(options?: OTLPTraceExporterOptions)

OTLPTraceExporterOptions

Field Type Default Description
url string 'http://localhost:4318/v1/traces' OTLP traces endpoint
headers Record<string, string> {} Additional HTTP headers
timeoutMs number 5000 Request timeout in milliseconds
fetcher typeof fetch global fetch Custom fetch implementation for testing

Timestamp conversion

Span startTime and endTime are stored in milliseconds. The exporter converts them to nanoseconds (ms * 1_000_000) for the OTLP startTimeUnixNano / endTimeUnixNano fields, which is the unit expected by the OTLP specification.

Wire format

{
  "resourceSpans": [{
    "scopeSpans": [{
      "scope": { "name": "@tagna/udiot" },
      "spans": [{
        "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
        "spanId": "00f067aa0ba902b7",
        "parentSpanId": "b9c7c989f97918e1",
        "name": "processOrder",
        "kind": "server",
        "startTimeUnixNano": 1711094400000000000,
        "endTimeUnixNano": 1711094400142000000,
        "attributes": { "orderId": "ord_xyz" },
        "status": { "code": "ok" }
      }]
    }]
  }]
}

Example: configuring a trace receiver

import {
  OTLPTraceExporter,
  registerTraceExporter,
} from '@tagna/udiot/telemetry';

registerTraceExporter(
  new OTLPTraceExporter({
    url: 'http://jaeger:4318/v1/traces',
  })
);

Configure and test the receiving service independently; a successful HTTP request alone does not prove the payload was accepted or retained.


ConsoleMetricsExporter

Writes metric snapshots as structured JSON log lines. Designed for local development and CI debugging where you want to see metric data without running a collector.

Constructor

new ConsoleMetricsExporter(options?: ConsoleMetricsExporterOptions)

ConsoleMetricsExporterOptions

Field Type Default Description
sink (payload: ConsoleMetricsPayload) => void telemetry logger .info() Custom output function
minMetrics number 1 Skip export if snapshot has fewer than this many metrics

ConsoleMetricsPayload

interface ConsoleMetricsPayload {
  timestamp: string;      // ISO 8601
  metrics: MetricSnapshot;
}

Default output

When the default sink is used, the exporter calls getTelemetryLogger().info('metrics.snapshot', { metricsCount, metrics }), which produces:

{
  "level": "info",
  "message": "metrics.snapshot",
  "timestamp": "2026-03-22T10:00:00.000Z",
  "attributes": {
    "metricsCount": 3,
    "metrics": [...]
  }
}

Example: custom sink for pretty-printing

import { ConsoleMetricsExporter } from '@tagna/udiot/telemetry';

const exporter = new ConsoleMetricsExporter({
  sink: (payload) => {
    for (const m of payload.metrics) {
      console.log(`[${payload.timestamp}] ${m.name} (${m.type}): ${m.lastValue}`);
    }
  },
  minMetrics: 2,
});

Example: initializing the SDK with all three exporters

import {
  initializeTelemetry,
  shutdownTelemetry,
  OTLPMetricsExporter,
  OTLPTraceExporter,
  ConsoleMetricsExporter,
  registerTraceExporter,
} from '@tagna/udiot/telemetry';

const isProduction = process.env.NODE_ENV === 'production';
const collectorBase = process.env.OTEL_COLLECTOR_URL ?? 'http://localhost:4318';

// Metrics exporters
const metricsExporters = [
  new OTLPMetricsExporter({ url: `${collectorBase}/v1/metrics` }),
];
if (!isProduction) {
  metricsExporters.push(new ConsoleMetricsExporter());
}

initializeTelemetry({
  metrics: {
    exporters: metricsExporters,
    flushIntervalMs: 60_000,
  },
});

// Trace exporter
registerTraceExporter(
  new OTLPTraceExporter({ url: `${collectorBase}/v1/traces` })
);

// Graceful shutdown
process.on('SIGTERM', async () => {
  await shutdownTelemetry();
  process.exit(0);
});

Common Patterns

Authentication — pass the headers required by your verified receiver through the headers option. Keep credentials in server-side secret configuration and verify the receiver's current authentication requirements independently.

Custom fetch — pass a fetcher option to inject a custom fetch implementation. This is the standard way to test exporters without making real HTTP calls, or to add retry logic.

Disabling in tests — do not register any exporters in your test setup. Without exporters, flush() is a no-op and finalizeSpan skips export silently.

Pitfalls

  • Both OTLP exporters skip export silently if the request is aborted by the timeout. Check your collector's ingestion rate if you notice missing data under high load — the 5000 ms default may need to be raised.
  • ConsoleMetricsExporter with minMetrics: 0 or minMetrics: 1 (the default) will emit an entry even for a single metric. Set a higher minMetrics value if you want to suppress noisy early-startup exports.
  • The OTLPTraceExporter exports one span per HTTP request. Under very high trace volume this can generate many small requests. Consider deploying an OpenTelemetry Collector in front of your backend to batch and compress spans.
From the canonical UDIOT public documentation.Try it in the playground ↗