Back to Docs

Node.js Integration Guide

Send logs from your Node.js application to logging.cheap using the OpenTelemetry SDK and OTLP (OpenTelemetry Protocol). This guide uses the OpenTelemetry Logs SDK with an OTLP HTTP exporter to send log records directly to logging.cheap. You can emit logs via the OpenTelemetry API or bridge console.info() so your existing code automatically exports logs.

How it works: Your app → OpenTelemetry Logs SDK → OTLP over HTTP → logging.cheap. No agents to install, no collectors to manage, no YAML to configure.

Prerequisites

Get an API Key

Create an API key from your Settings page. Copy the key — it starts with cl_live_ and is only shown once. If you just signed up, an API key prompt appears on your first login.

Create a New Project

If you're starting from scratch, create a new directory and initialize a Node.js project:

terminal
mkdir my-app && cd my-app
npm init -y

Install Dependencies

You need four packages: the OpenTelemetry Logs API, the Logs SDK, the OTLP HTTP exporter, and the resources package for identifying your service:

terminal
npm install @opentelemetry/api-logs \
            @opentelemetry/sdk-logs \
            @opentelemetry/exporter-logs-otlp-http \
            @opentelemetry/resources

The @opentelemetry/exporter-logs-otlp-http package sends log data over HTTP using JSON encoding, which logging.cheap accepts natively.

Set Environment Variables

The OpenTelemetry SDK reads its configuration from standard OTEL_* environment variables — no hardcoded endpoints or secrets in your code. Set these before running your application:

terminal
# Required: where to send logs
export OTEL_EXPORTER_OTLP_ENDPOINT="https://logging.cheap"

# Required: authenticate with your API key
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer cl_live_YOUR_API_KEY"

# Required: name your service (appears in the log viewer)
export OTEL_SERVICE_NAME="my-node-app"

# Recommended: disable traces and metrics (logging.cheap only accepts logs)
export OTEL_TRACES_EXPORTER="none"
export OTEL_METRICS_EXPORTER="none"

Replace cl_live_YOUR_API_KEY with the key you copied earlier, and my-node-app with a name that identifies your application in the log viewer (e.g. api-server, checkout-service). Make sure to include the Bearer prefix — without it, authentication will fail.

Initialize OpenTelemetry Logging

Create a file called index.js. The OTLP exporter reads configuration from the environment variables above:

index.js
const { SeverityNumber } = require('@opentelemetry/api-logs');
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
const { resourceFromAttributes } = require('@opentelemetry/resources');

// 1. Create a resource describing your service
const resource = resourceFromAttributes({
    'service.name': process.env.OTEL_SERVICE_NAME || 'my-node-app',
});

// 2. Set up the logger provider with an OTLP exporter
const logExporter = new OTLPLogExporter();
const loggerProvider = new LoggerProvider({
    resource,
    processors: [new BatchLogRecordProcessor(logExporter)],
});

const logger = loggerProvider.getLogger('my-app');

// 3. Emit a test log
logger.emit({
    severityNumber: SeverityNumber.INFO,
    severityText: 'INFO',
    body: 'Hello from logging.cheap!',
    attributes: {
        environment: 'production',
        version: '1.0.0',
    },
});

// 4. Flush logs before exiting (important for short-lived scripts)
loggerProvider.shutdown().catch(console.error);

Run It

terminal
node index.js

If everything is configured correctly, the script will run without errors. Behind the scenes, the BatchLogRecordProcessor batches your log and sends it to https://logging.cheap/v1/logs with your API key.

Understanding the Code

Here's what each piece does, if you're new to OpenTelemetry:

Using with Express or Long-Running Apps

For web applications that run continuously, set up OpenTelemetry once at startup and register a shutdown hook to flush logs when the process exits. A convenient pattern is to bridge console.info() to OpenTelemetry, so every console.info() call in your application automatically exports a log record.

First, install Express alongside the OpenTelemetry packages:

terminal
npm install express
index.js
const { SeverityNumber } = require('@opentelemetry/api-logs');
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
const { resourceFromAttributes } = require('@opentelemetry/resources');

// Set up OpenTelemetry logging
const resource = resourceFromAttributes({
    'service.name': process.env.OTEL_SERVICE_NAME || 'my-express-app',
});

const logExporter = new OTLPLogExporter();
const loggerProvider = new LoggerProvider({
    resource,
    processors: [new BatchLogRecordProcessor(logExporter)],
});

const otelLogger = loggerProvider.getLogger('my-express-app');

// Bridge console.info to OpenTelemetry — every console.info() call
// now both prints to the terminal and exports an OTel log record.
const originalInfo = console.info.bind(console);
console.info = (...args) => {
    originalInfo(...args);
    otelLogger.emit({
        severityNumber: SeverityNumber.INFO,
        severityText: 'INFO',
        body: args.map(String).join(' '),
    });
};

// Your Express app
const express = require('express');
const app = express();

app.get('/', (req, res) => {
    console.info('Received a request');
    res.send('Hello, world!');
});

const port = process.env.PORT || 3000;
const server = app.listen(port, () => {
    // console.log is NOT bridged — only console.info sends logs to logging.cheap
    console.log(`Listening on port ${port}`);
});

// Graceful shutdown — flush pending logs before exiting
const shutdown = () => {
    server.close(() => {
        loggerProvider.shutdown().then(() => process.exit(0));
    });
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);

The console.info bridge intercepts every console.info() call, prints it to the terminal as usual, and also emits an OpenTelemetry log record. You can apply the same pattern to console.warn and console.error using SeverityNumber.WARN and SeverityNumber.ERROR respectively. The SIGTERM and SIGINT handlers ensure that all pending logs are flushed when your application stops. Without them, the last batch of logs may be lost.

Verify in the Log Viewer

Navigate to the log viewer, select a time range that covers the last few minutes, and search for your test message. You should see your "Hello from logging.cheap!" log entry with the service name you set in OTEL_SERVICE_NAME and severity INFO.

If logs don't appear immediately, wait a few seconds for the batch processor to flush, then refresh the page.

Troubleshooting

Next Steps

Your Node.js application is now sending logs to logging.cheap. In a production application, initialize the logger provider once at startup and register signal handlers for graceful shutdown. If you use the console.info bridge pattern, all console.info() calls throughout your codebase will be exported automatically. For structured logging, consider using a library like Pino or Winston with their respective OpenTelemetry transports.

Head to the log viewer to search and explore your logs. For more on the OpenTelemetry JavaScript SDK, see the official OpenTelemetry JavaScript documentation.