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
- Node.js 18.19 or later (18.19+, 20.6+, or 22+)
- npm (comes with Node.js)
- A logging.cheap account
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:
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:
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:
# 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:
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
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:
resourceFromAttributes()— creates metadata about your application (service name, version, etc.). Theservice.nameattribute shows up as the service name in your log viewer.LoggerProvider— the central object that manages log processing. You attach processors and exporters to it.BatchLogRecordProcessor— collects log records and sends them in batches rather than one at a time. This is more efficient and reduces network overhead.OTLPLogExporter()— sends logs over HTTP using JSON encoding. It readsOTEL_EXPORTER_OTLP_ENDPOINTandOTEL_EXPORTER_OTLP_HEADERSfrom the environment automatically.logger.emit()— sends a single log record with a severity level, message body, and optional attributes.loggerProvider.shutdown()— flushes all pending logs and closes the exporter. Returns a Promise. For short-lived scripts, the Node.js event loop stays alive until the HTTP request completes.
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:
npm install express
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
- 401 Unauthorized — check that your
OTEL_EXPORTER_OTLP_HEADERSincludesBearerbefore your API key (with a space after "Bearer"). - No logs appearing — make sure you call
loggerProvider.shutdown()before your process exits. The batch processor sends logs asynchronously, so without shutdown the process may exit before logs are flushed. - Connection errors — verify that
OTEL_EXPORTER_OTLP_ENDPOINTis set tohttps://logging.cheap(nothttp://). The SDK retries failed requests automatically, so transient network issues are handled. - Program exits before logs are sent — the batch processor buffers logs and sends them periodically. Always call
loggerProvider.shutdown()before exit. For short-lived scripts, shutdown is essential. For long-running apps, use signal handlers as shown in the Express example above. - Wrong exporter package — use
@opentelemetry/exporter-logs-otlp-http(HTTP transport), not@opentelemetry/exporter-logs-otlp-grpc(gRPC transport). logging.cheap accepts OTLP over HTTP.
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.