axonpush
TypeScript SDKIntegrations

OpenTelemetry

AxonPushSpanExporter is a SpanExporter for any OTel tracer provider, add it alongside your existing exporters and spans land in axonpush as app.span events.

If your service is already instrumented with OpenTelemetry, add AxonPushSpanExporter to its tracer provider. Every span ships to axonpush as an app.span event with an OTLP-shaped JSON payload, in parallel with whatever other backends you export to.

Tested against @opentelemetry/api@^1.7 and @opentelemetry/sdk-trace-base@^1.18, both optional peer dependencies.

For tracing GenAI model calls, the recommended path is now OpenTelemetry-native telemetry, which emits standard gen_ai.* spans over OTLP. This exporter (which ships spans as proprietary app.span events) still works and is supported, but is superseded for new code.

Install

npm install @axonpush/sdk @opentelemetry/api @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-node

Register the exporter

import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { AxonPush } from "@axonpush/sdk";
import { AxonPushSpanExporter } from "@axonpush/sdk/integrations/otel";

const client = new AxonPush();
const exporter = new AxonPushSpanExporter({
  client,
  channelId: process.env.AXONPUSH_CHANNEL_ID!,
  serviceName: "my-api",
  serviceVersion: process.env.npm_package_version,
  environment: "production",
});

const provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();

A tracer provider accepts several processors, so this sits alongside an existing OTLP exporter rather than replacing it:

provider.addSpanProcessor(new SimpleSpanProcessor(otlpExporter));
provider.addSpanProcessor(new SimpleSpanProcessor(axonpushExporter));

Instrument as usual, spans export when they end:

import { trace } from "@opentelemetry/api";

const tracer = trace.getTracer("my-api");

await tracer.startActiveSpan("POST /chat", async (span) => {
  span.setAttribute("http.method", "POST");
  await callModel();
  span.end();
});

Prefer the native OTLP endpoint for raw throughput

This exporter publishes one client.events.publish(...) call per span. The axonpush backend also speaks OTLP/HTTP directly at /v1/traces (and /v1/logs), accepting protobuf or JSON. If you are already running a collector, point it there instead, you get batching and compression that a per-span exporter cannot.

Options

OptionTypeDefaultDescription
clientAxonPushrequiredThe SDK client to publish through.
channelIdstringrequiredChannel UUID for span events.
serviceNamestringOverrides service.name on the span’s resource.
serviceVersionstringOverrides service.version.
environmentstringOverrides deployment.environment.
mode"background" | "sync" | "bullmq""background"Publishing strategy.
queueSizenumber1000Spans buffered before overflow.
overflowPolicy"drop-oldest" | "drop-newest" | "block""drop-oldest"What a full queue does.
shutdownTimeoutMsnumber2000Drain budget on shutdown().
concurrencynumber1Parallel in-flight publishes.
bullmqOptionsBullMQPublisherOptionsRequired when mode: "bullmq".

The three resource options are merged over the span’s own resource attributes, so they win where they overlap.

Flushing and shutdown

await exporter.forceFlush();  // OTel's hook - drains the queue
await exporter.shutdown();    // drains and stops the loop
await exporter.flush(2000);   // same as forceFlush, with a timeout

export() returns success as soon as spans are queued, not once they are delivered, the OTel result callback fires immediately. provider.shutdown() calls exporter.shutdown(), so a graceful exit flushes everything pending. In a Lambda handler, call forceFlush() at the end of each invocation or wrap the handler with flushAfterInvocation, re-exported from this module. See the pino serverless section.

Auto-instrumentation

OTel’s auto-instrumentation packages hook into the provider you already configured, so they need no axonpush-specific wiring:

npm install @opentelemetry/auto-instrumentations-node
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";

registerInstrumentations({
  tracerProvider: provider,
  instrumentations: [getNodeAutoInstrumentations()],
});

Event shape

FieldValue
identifierthe span name
eventType"app.span"
traceId / spanId / parentSpanIdlifted onto the event envelope, not just the payload
payload.namespan name
payload.kindthe numeric OTel SpanKind
payload.startTimeUnixNano / endTimeUnixNanohrtime pairs converted to nanosecond strings
payload.flagstraceFlags from the span context
payload.status{ code, message }, defaulting to code: 0 and an empty message
payload.attributesevery span attribute
payload.events / payload.linkspresent only when non-empty
payload.resourcespan resource merged with the exporter’s overrides
payload.scopeinstrumentation scope { name, version }
metadata.framework"opentelemetry"

payload.kind is the raw enum value the OTel SDK sets, 0 for INTERNAL, 1 SERVER, 2 CLIENT, 3 PRODUCER, 4 CONSUMER, not a string. Parent span id is read from parentSpanContext.spanId first, falling back to the older parentSpanId field, so both recent and older SDK versions work.