Shipping a Node.js SaaS app without observability is like flying a plane with no instruments — everything feels fine until it suddenly isn't. By the time a user files a support ticket, the evidence is already gone. The fix is not a better logging library bolted on after your first outage; it's a structured observability strategy baked in from the first commit.
OpenTelemetry (OTel) gives you a vendor-neutral SDK to capture the three pillars of observability — traces, metrics, and logs — in one coherent framework. Paired with Grafana Cloud's generous free tier, you get a production-grade observability stack at zero initial cost.
Here is how to set it up properly.
Why OpenTelemetry Over Traditional Monitoring
Most monitoring tools lock you into their agent and their data format. Swap vendors and you rewrite your instrumentation. OpenTelemetry solves this with an open standard: one SDK, one wire protocol (OTLP), and a collector that can fan data out to any backend — Grafana, Datadog, Honeycomb, or your own infrastructure.
For SaaS teams, this matters because:
- You avoid vendor lock-in at the instrumentation layer.
- Your telemetry data model stays consistent across services.
- You can start free and migrate backends without touching application code.
Step 1: Install the OpenTelemetry SDK
Start with a clean Node.js project (Express or Fastify both work). Install the core packages:
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/exporter-metrics-otlp-http \
@opentelemetry/sdk-metrics \
@opentelemetry/resources \
@opentelemetry/semantic-conventions
auto-instrumentations-node is the force multiplier here — it automatically patches Express, HTTP, database clients (pg, mongoose, redis), and more. You get distributed traces across your entire request lifecycle with near-zero manual effort.
Step 2: Write a Dedicated Instrumentation Bootstrap File
Create src/instrumentation.ts (or .js) and load it before any other module. This ordering is critical — OTel patches libraries at import time.
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Resource } from '@opentelemetry/resources';
import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
const resource = new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'my-saas-api',
[SEMRESATTRS_SERVICE_VERSION]: process.env.APP_VERSION ?? '0.0.1',
});
const traceExporter = new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
headers: { Authorization: `Bearer ${process.env.GRAFANA_API_KEY}` },
});
const metricExporter = new OTLPMetricExporter({
url: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
headers: { Authorization: `Bearer ${process.env.GRAFANA_API_KEY}` },
});
const sdk = new NodeSDK({
resource,
traceExporter,
metricReader: new PeriodicExportingMetricReader({
exporter: metricExporter,
exportIntervalMillis: 15_000,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());
Load this file first via your start command: node --require ./dist/instrumentation.js dist/server.js. If you use ts-node, add it to the require array in your tsconfig or .env.
Step 3: Connect to Grafana Cloud
Sign up at grafana.com — the free tier covers 50 GB of logs, 10,000 series of metrics, and 50 GB of traces per month. That is comfortably enough for an early-stage SaaS product handling thousands of daily active users.
In your Grafana Cloud portal:
- Navigate to Connections → Add new connection → OpenTelemetry.
- Grafana will generate OTLP endpoint URLs and an API key scoped to write access.
- Drop those values into your environment variables (
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,GRAFANA_API_KEY).
No collector sidecar is required at this scale. For larger deployments, run the OpenTelemetry Collector as a sidecar or DaemonSet and route all signals through it — this decouples retry logic, batching, and backend routing from your application process.
Step 4: Add Custom Business Metrics
Auto-instrumentation covers infrastructure-level signals — request latency, error rates, DB query duration. For SaaS, you also need business-level metrics: trial conversions, feature usage, subscription events.
Add these with the OTel Metrics API:
import { metrics } from '@opentelemetry/api';
const meter = metrics.getMeter('saas-business-metrics');
const trialSignups = meter.createCounter('saas.trial.signups', {
description: 'Number of new trial account registrations',
});
// In your signup handler:
trialSignups.add(1, { plan: 'pro', region: 'west-africa' });
This single pattern gives you a queryable time series you can alert on — "trial signups dropped 40% in the last hour" — before any customer notices.
Step 5: Correlate Logs with Traces
Logs without trace context are noise. Add the current trace ID to every log line so you can jump from a log entry directly to the full distributed trace in Grafana Tempo.
If you use pino (recommended for Node.js performance):
import { trace } from '@opentelemetry/api';
import pino from 'pino';
const logger = pino({
mixin() {
const span = trace.getActiveSpan();
if (!span) return {};
const { traceId, spanId } = span.spanContext();
return { traceId, spanId };
},
});
Every log line now carries traceId and spanId. In Grafana Loki, you can filter by trace ID and get the full request narrative in one click.
Step 6: Set Meaningful Alerts, Not Noisy Ones
Instrument first, alert second. With your data flowing into Grafana, create alerts on signals that actually matter:
- P95 latency on your most critical API routes exceeds 800 ms.
- Error rate on
/api/paymentsexceeds 1% over a 5-minute window. - Trial signup counter falls below historical baseline (anomaly detection).
Avoid alerting on CPU or memory in isolation — those are symptoms, not causes. Trace-based alerting tells you which service and which code path is responsible.
The Mindset Shift That Changes Everything
Observability is not a DevOps concern — it is a product quality concern. When your SaaS users experience a slow checkout or a failed API call, they do not open a ticket; they churn. The teams that catch regressions in staging, before a feature flag rollout reaches 100% of users, are the teams that retain customers.
OpenTelemetry gives you the instrumentation standard. Grafana Cloud gives you the backend. The only thing left is the discipline to treat telemetry as a first-class feature of every pull request.
Why This Matters for Your Project
Whether you are building a two-person startup or a growing SaaS platform, the cost of reactive monitoring — engineering time, customer trust, revenue loss — always exceeds the cost of proactive instrumentation. Setting up OpenTelemetry from day one means every new service, every new developer, and every new feature inherits a consistent observability baseline automatically. That is the kind of engineering leverage that compounds over time.





