Instrumenting your application
Your application needs to include the OpenTelemetry SDK to send traces and metrics to the Alokai collector. This page covers the Node.js setup, which applies to both the vue-storefront and middleware services.
Enable at least one observability backend in the Console before instrumenting your application. The collector is only deployed when a backend is configured — sending telemetry to it without an active backend will result in data being silently dropped.
Install the SDK
The exporter package determines the transport. Use the HTTP/protobuf packages (recommended) or the gRPC packages — they are not interchangeable.
HTTP/protobuf (recommended):
npm install \
@opentelemetry/sdk-node \
@opentelemetry/api \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-proto \
@opentelemetry/exporter-metrics-otlp-proto
gRPC:
npm install \
@opentelemetry/sdk-node \
@opentelemetry/api \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpc \
@opentelemetry/exporter-metrics-otlp-grpc
Create an instrumentation file
The instrumentation file imports from whichever exporter package you installed. The examples below are equivalent except for the transport.
HTTP/protobuf:
// instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter(),
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start()
.then(() => console.log('OpenTelemetry SDK started'))
.catch((err) => console.error('Failed to start OpenTelemetry SDK', err));
// Flush and shut down cleanly on container stop
const shutdown = () =>
sdk.shutdown()
.then(() => console.log('OpenTelemetry SDK shut down'))
.catch((err) => console.error('Error shutting down OpenTelemetry SDK', err))
.finally(() => process.exit(0));
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
gRPC:
// instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter(),
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start()
.then(() => console.log('OpenTelemetry SDK started'))
.catch((err) => console.error('Failed to start OpenTelemetry SDK', err));
const shutdown = () =>
sdk.shutdown()
.then(() => console.log('OpenTelemetry SDK shut down'))
.catch((err) => console.error('Error shutting down OpenTelemetry SDK', err))
.finally(() => process.exit(0));
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
The SIGTERM/SIGINT handlers ensure the SDK flushes any buffered spans and metrics before the container exits. Without this, telemetry from the last few seconds before a deployment or scale-down may be lost.
Load the instrumentation file
Pass it via the --import flag before your application module (Node.js 18+):
node --import ./instrumentation.js server.js
Or set it via NODE_OPTIONS in your environment or Dockerfile so it applies to all node processes:
NODE_OPTIONS="--import ./instrumentation.js"
Environment variables
Configure the SDK by setting these environment variables on your application via the Console:
HTTP/protobuf (recommended)
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
OTEL_SERVICE_NAME=<your-service-name>
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=<environment>,service.version=<version>
gRPC
OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317
OTEL_SERVICE_NAME=<your-service-name>
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=<environment>,service.version=<version>
Note that OTEL_EXPORTER_OTLP_PROTOCOL is not needed here — the transport is determined at build time by whichever exporter package you installed, not by an environment variable.
deployment.environment and service.version must be set through OTEL_RESOURCE_ATTRIBUTES using those exact key names. Setting a custom environment variable such as DEPLOYMENT_ENV will not be picked up by the OTel SDK and will be silently ignored — causing traces to appear with fallback values in your observability backend.
Reference
| Variable | Description |
|---|---|
OTEL_SERVICE_NAME | Identifies your service in traces and metrics (e.g. storefront, middleware). Defaults to alokai-cloud if not set. |
OTEL_EXPORTER_OTLP_ENDPOINT | Collector address. Use http://otel-collector:4318 for HTTP/protobuf or otel-collector:4317 for gRPC. |
OTEL_RESOURCE_ATTRIBUTES | Comma-separated key=value pairs attached to every span and metric. |
OTEL_TRACES_SAMPLER | Sampling strategy. See Sampling. |
OTEL_TRACES_SAMPLER_ARG | Sampling ratio for parentbased_traceidratio (e.g. 0.1 = 10%). |
Auto-instrumentation
The @opentelemetry/auto-instrumentations-node package instruments the most common Node.js libraries automatically — no manual span creation needed for:
- HTTP and HTTPS (
http,https,fetch) - Express, Fastify, Koa, Hapi
graphql- Database clients:
pg,mysql2,mongodb,redis,ioredis - gRPC
aws-sdk
Each instrumented library produces spans automatically, with attributes populated from the request/response (method, URL, status code, query text, etc.).
Custom spans
Use the @opentelemetry/api package to create spans for your own code:
import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('my-module');
async function fetchProducts(category: string) {
return tracer.startActiveSpan(`fetchProducts`, async (span) => {
span.setAttribute('category', category);
try {
const result = await catalogClient.getProducts(category);
span.setAttribute('result.count', result.length);
return result;
} catch (err) {
span.recordException(err);
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
throw err;
} finally {
span.end();
}
});
}
Active spans are automatically linked to the parent trace — any span created inside startActiveSpan becomes a child of the currently active span, forming a complete trace tree.
Custom metrics
Use the @opentelemetry/api package to record custom measurements:
import { metrics } from '@opentelemetry/api';
const meter = metrics.getMeter('my-module');
// Counter — monotonically increasing value
const checkoutCounter = meter.createCounter('checkout.initiated', {
description: 'Number of checkout sessions started',
});
// Histogram — distribution of values (use for latency, sizes, values)
const cartValueHistogram = meter.createHistogram('checkout.cart_value', {
description: 'Value of carts at checkout',
unit: 'USD',
});
// In your checkout handler:
checkoutCounter.add(1, { currency: 'USD', channel: 'web' });
cartValueHistogram.record(cart.total, { currency: cart.currency });
Only custom application metrics emitted by your code are supported. Default Kubernetes infrastructure metrics (CPU, memory, network) are collected separately at the platform level and are not forwarded through the OTel pipeline.