Vue Storefront is now Alokai! Learn More
Instrumenting your application

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:

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

VariableDescription
OTEL_SERVICE_NAMEIdentifies your service in traces and metrics (e.g. storefront, middleware). Defaults to alokai-cloud if not set.
OTEL_EXPORTER_OTLP_ENDPOINTCollector address. Use http://otel-collector:4318 for HTTP/protobuf or otel-collector:4317 for gRPC.
OTEL_RESOURCE_ATTRIBUTESComma-separated key=value pairs attached to every span and metric.
OTEL_TRACES_SAMPLERSampling strategy. See Sampling.
OTEL_TRACES_SAMPLER_ARGSampling 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.