@svantic/telemetry
v1.4.3
Published
OTEL-based observability for any Node.js service — standalone, no dependency on @svantic/sdk. Provides W3C-compliant tracing, LLM usage tracking, and context propagation with pluggable auth and export.
Downloads
1,770
Maintainers
Readme
@svantic/telemetry
Standalone OTEL-based observability for any Node.js service. No dependency on @svantic/sdk — use it in agents, microservices, or any process that needs W3C-compliant distributed tracing with pluggable export and authentication.
Purpose
- W3C-compliant distributed tracing (32-char hex trace IDs, 16-char hex span IDs).
- LLM usage tracking (token counts per model, per span).
- Context propagation via
traceparent+baggageheaders. - Pluggable authentication (API key, Bearer JWT, custom).
- Pluggable export (Svantic mesh, OTLP collector, console).
- Auto-instrumentations for LLM calls, A2A messages, and tool invocations.
Install
npm install @svantic/telemetryFor OTLP export (optional peer dependency):
npm install @opentelemetry/exporter-trace-otlp-httpIn the monorepo, depend via workspace or file:../../packages/telemetry from consuming packages.
Quick Start
Scenario 1 — Export to Svantic Mesh
Send spans and usage directly to a running mesh instance:
import { SvanticTelemetry } from '@svantic/telemetry';
const telemetry = SvanticTelemetry.init({
service_name: 'my-agent',
export: {
endpoint: 'http://localhost:7001/telemetry/otlp',
auth: { type: 'api_key', key: 'your-tenant-secret' },
},
});
const tracer = telemetry.get_tracer();
const span = tracer.start_span('handle-request');
// ... your logic ...
span.end();
await telemetry.shutdown();Scenario 2 — Export to an OTLP Collector
Route spans to any OpenTelemetry-compatible backend (Jaeger, Grafana Tempo, Datadog, etc.):
const telemetry = SvanticTelemetry.init({
service_name: 'billing-service',
export: {
endpoint: 'http://otel-collector:4318/v1/traces',
protocol: 'otlp',
auth: { type: 'bearer', token: 'my-token' },
},
});Scenario 3 — Console (local development)
Print spans to stdout for quick debugging:
const telemetry = SvanticTelemetry.init({
service_name: 'dev-agent',
export: { type: 'console' },
});Subpath Exports
| Import path | Role |
| --- | --- |
| @svantic/telemetry | Core: SvanticTelemetry, SvanticTracer, spans, ID generator. |
| @svantic/telemetry/auth | Auth providers: ApiKeyAuth, BearerAuth, CustomAuth. |
| @svantic/telemetry/propagation | W3C inject/extract: ContextInjector, ContextExtractor, BAGGAGE_KEYS. |
| @svantic/telemetry/exporters | Exporter implementations: MeshSpanExporter, OtlpExporterWrapper, ConsoleExporter, NoopExporter. |
| @svantic/telemetry/instrumentations | Auto-wrapping: LlmInstrumentation, A2aInstrumentation, ToolInstrumentation. |
Tracer API
All spans are created through SvanticTracer, obtained via telemetry.get_tracer().
Generic span
const span = tracer.start_span('my-operation', {
'my.attribute': 'value',
});
// ... work ...
span.end();LLM span
Captures model name and token usage alongside the span:
const llm_span = tracer.start_llm_span('gpt-4o');
// ... call the model ...
llm_span.end({ input_tokens: 120, output_tokens: 340 });Tool span
Tags the span with the tool name automatically:
const tool_span = tracer.start_tool_span('search_products', {
'svantic.tool.args': JSON.stringify({ query: 'shoes' }),
});
// ... execute tool ...
tool_span.end();Span nesting
Spans nest automatically via an internal stack. Starting a span while another is active makes it a child:
const parent = tracer.start_span('request');
const child = tracer.start_span('db-query'); // parented under "request"
child.end();
parent.end();Adopting a remote context
When receiving a request with a traceparent header, adopt the remote trace so local spans join the distributed trace:
import { ContextExtractor } from '@svantic/telemetry/propagation';
const extracted = ContextExtractor.extract(inbound_headers);
if (extracted) {
tracer.adopt_remote_context(extracted.trace_id, extracted.span_id);
}Authentication
Auth is configured via the export.auth block in TelemetryInitOptions. The resolved AuthProvider attaches headers to every export request.
| Type | Config | Header |
| --- | --- | --- |
| api_key | { type: 'api_key', key: 'secret' } | X-API-Key: secret |
| bearer | { type: 'bearer', token: 'jwt', refresh?: () => Promise<string> } | Authorization: Bearer jwt |
| custom | { type: 'custom', get_headers: () => Record<string, string> } | Whatever get_headers returns. |
| none | { type: 'none' } | No auth headers. |
Context Propagation
Svantic uses W3C Trace Context for distributed tracing across service boundaries.
Injecting headers (outbound)
import { ContextInjector } from '@svantic/telemetry/propagation';
const headers: Record<string, string> = {};
ContextInjector.inject(headers, {
trace_id: '0af7651916cd43dd8448eb211c80319c',
span_id: 'b7ad6b7169203331',
baggage: {
'svantic.session_id': 'sess-123',
'svantic.tenant_id': 'acme',
},
});
// headers now contains:
// traceparent: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
// baggage: "svantic.session_id=sess-123,svantic.tenant_id=acme"Extracting headers (inbound)
import { ContextExtractor } from '@svantic/telemetry/propagation';
const ctx = ContextExtractor.extract(request.headers);
if (ctx) {
// ctx.trace_id — 32-char hex
// ctx.span_id — 16-char hex
// ctx.baggage — { 'svantic.session_id': 'sess-123', ... }
}Well-known baggage keys
| Key | Purpose |
| --- | --- |
| svantic.session_id | Ties spans to a dashboard session. |
| svantic.tenant_id | Multi-tenancy scoping. |
| svantic.invocation_id | Links a specific tool call to remote execution. |
| svantic.agent_type | Identifies the agent type in the DAG. |
| svantic.instance_id | Pinpoints the specific pod/process. |
Instrumentations
Convenience wrappers that pair tracer span lifecycle with common call patterns. Each instrumentation accepts a tracer and wraps a function so spans are created and ended automatically.
| Class | What it wraps |
| --- | --- |
| LlmInstrumentation | LLM model calls — creates an LLM span, records token usage on completion. |
| A2aInstrumentation | Agent-to-agent messages — creates a span with the remote agent URL and capability name. |
| ToolInstrumentation | Tool invocations — creates a tool span with args/result captured as attributes. |
Integration with Cliq
Cliq (the Svantic terminal client) uses @svantic/telemetry for tracing agent interactions:
import { SvanticTelemetry } from '@svantic/telemetry';
const telemetry = SvanticTelemetry.init({
service_name: 'cliq',
export: {
endpoint: 'http://localhost:7001/telemetry/otlp',
auth: { type: 'api_key', key: 'tenant-secret' },
},
});
const tracer = telemetry.get_tracer();
const span = tracer.start_span('user-ask');
// ... send message to mesh, receive response ...
span.end();Architecture
@svantic/telemetry is a standalone package at the bottom of the dependency graph. It has no dependency on @svantic/sdk, @svantic/shared, or any service.
@svantic/sdk ──uses──▶ @svantic/telemetry
│
├── core/ SvanticTelemetry, SvanticTracer, spans, ID gen
├── auth/ ApiKeyAuth, BearerAuth, CustomAuth
├── propagation/ ContextInjector, ContextExtractor, BAGGAGE_KEYS
├── exporters/ MeshSpanExporter, OtlpExporterWrapper, ConsoleExporter, NoopExporter
└── instrumentations/ LlmInstrumentation, A2aInstrumentation, ToolInstrumentationSpan data flows outward through one of the pluggable exporters:
- MeshSpanExporter — HTTP POST to
/telemetry/reporton the mesh. - OtlpExporterWrapper — delegates to
@opentelemetry/exporter-trace-otlp-httpfor any OTLP-compatible backend. - ConsoleExporter — prints spans to stdout (development).
- NoopExporter — discards spans (testing).
Build
From packages/telemetry:
npm install
npm run buildTests
npm testTest suites cover core tracing, ID generation, all three auth providers, inject/extract propagation, and every exporter and instrumentation.
Key Environment Variables
Runtime variables depend on the export target. Configure them in the host application or deployment manifest:
| Variable | Purpose |
| --- | --- |
| Mesh endpoint URL | Target for MeshSpanExporter (e.g. http://localhost:7001/telemetry/report). |
| OTLP endpoint URL | Target for OtlpExporterWrapper (e.g. http://otel-collector:4318/v1/traces). |
| API key / Bearer token | Credentials for authenticated export. |
