JS Logs Interceptor
Elven Observability Official Logger
A high-performance log, event, and exception collector for Node.js apps that sends everything to Grafana Loki. It offers automatic console interception, support for dynamic labels, asynchronous sending with optimized buffering, and instant integration with the Elven Stack.
Features
High performance with asynchronous buffer, batch sending, and gzip compression.
Automatically captures:
console.log
,console.error
,console.warn
, etcUnhandled exceptions and rejected promises
Custom event via
trackEvent
Support for static and dynamic labels (with trace/span ID via OpenTelemetry).
Smart buffer with configurable batch sending.
Resilient to network failures with automatic retry.
Compatible with both simple and complex apps (web servers, workers, CLI).
Delivery guarantee on process shutdown.
Gzip compression to reduce bandwidth usage.
Real-time metrics and integrated health check.
Installation
npm install logs-interceptor
Basic Usage
import { init, logger } from "logs-interceptor";
init({
transport: {
url: "<https://loki.elvenobservability.com/loki/api/v1/push>",
tenantId: "your-tenant", // Your Tenant ID
authToken: "your-token", // Your JWT
compression: true, // Gzip enabled
},
appName: "my-js-app",
environment: "production",
interceptConsole: true,
labels: { env: "production" },
dynamicLabels: {
hostname: () => require('os').hostname(),
memory: () => Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + 'MB'
},
buffer: {
maxSize: 100,
flushInterval: 5000,
}
});
console.log("This will be sent to Loki!");
logger.trackEvent("registered_user", { user_id: 123 });
throw new Error("Test error!"); // Automatically captured
Configuration Options
Transport
url
string
Loki endpoint (Push API)
—
tenantId
string
Used in the X-Scope-OrgID header
—
authToken
string
JWT token for authentication
undefined
timeout
number
HTTP timeout in ms
5000
maxRetries
number
Retries in case of failure
3
compression
boolean
Enables gzip compression
true
Application
appName
string
Application name (fixed label)
—
version
string
Application version
“1.0.0”
environment
string
Environment (prod, dev, staging)
“production”
labels
object
Fixed labels per log
{}
dynamicLabels
object
Dynamically generated labels
{}
Buffer & Performance
buffer.maxSize
number
Logs in the buffer before forcing send
100
buffer.flushInterval
number
Maximum time before sending (ms)
5000
buffer.autoFlush
boolean
Auto flush enabled
true
interceptConsole
boolean
Automatically captures console.*
false
enableMetrics
boolean
Collects performance metrics
true
Filtering & Sampling
filter.levels
array
Allowed log levels
[‘debug’,’info’,’warn’,’error’,’fatal’]
filter.samplingRate
number
Sampling rate (0.0–1.0)
1.0
filter.maxMessageLength
number
Maximum message size
8192
Example with Express.js
import express from "express";
import { init, logger } from "logs-interceptor";
init({
transport: {
url: "<https://loki.elvenobservability.com/loki/api/v1/push>",
tenantId: "your-tenantId",
authToken: "your-token",
compression: true,
},
appName: "express-app",
environment: "production",
interceptConsole: true,
labels: {
service: "api",
env: "production"
},
dynamicLabels: {
hostname: () => require('os').hostname(),
uptime: () => Math.round(process.uptime()) + 's'
},
});
const app = express();
app.get("/ping", (req, res) => {
console.log("Ping called"); // Automatically intercepted
logger.trackEvent("api_ping", { ip: req.ip });
res.send({ message: "pong" });
});
app.get("/erro", () => {
throw new Error("Intentional error"); // Automatically captured
});
app.listen(3000, () => {
console.log("Server started on port 3000");
});
Auto-initialization with NODE_OPTIONS
For existing apps without code modification:
NODE_OPTIONS="--require logs-interceptor/preload" \\
LOGS_INTERCEPTOR_URL="<https://loki.elvenobservability.com/loki/api/v1/push>" \\
LOGS_INTERCEPTOR_TENANT_ID="your-tenant" \\
LOGS_INTERCEPTOR_APP_NAME="my-api" \\
LOGS_INTERCEPTOR_AUTH_TOKEN="your-token" \\
LOGS_INTERCEPTOR_ENVIRONMENT="production" \\
node app.js
Metrics & Monitoring
import { logger } from "logs-interceptor";
// Real-time metrics
const metrics = logger.getMetrics();
console.log({
logsProcessed: metrics.logsProcessed,
logsDropped: metrics.logsDropped,
flushCount: metrics.flushCount,
avgFlushTime: metrics.avgFlushTime,
errorCount: metrics.errorCount
});
// Health check
const health = logger.getHealth();
console.log({
healthy: health.healthy,
uptime: health.uptime,
bufferUtilization: health.bufferUtilization
});
Best Practices
Short scripts (CLI, cron)
Use await logger.flush()
before the exit.
Web apps or workers
The default setup already handles background sending.
High concurrency
Use maxSize: 500+
and flushInterval: 10000.
Environments with OpenTelemetry
Labels trace_id
and span_id
are captured automatically.
Production
Enable compression: true
e samplingRate < 1.0
if needed.
Troubleshooting
Logs are not showing up
tenantId
, authToken
or Incorrect URLs
Check the configuration and set debug: true
."
Incomplete logs
Process terminating quickly
Use await logger.flush()
before exiting
HTTP 400
Malformed payload
Check if Loki is running at the URL
Poor performance
Buffer too small
Increase maxSize
and flushInterval
Manual Flush
import { logger } from "logs-interceptor";
// Force immediate send
await logger.flush();
// Exemplo com graceful shutdown
process.on('SIGTERM', async () => {
console.log('Shutting down gracefully...');
await logger.flush();
process.exit(0);
});
Custom Events
import { logger } from "logs-interceptor";
logger.trackEvent("active_user", {
user_id: "abc123",
source: "mobile",
action: "login"
});
logger.trackEvent("purchase_completed", {
order_id: "12345",
value: 99.90,
payment_method: "credit_card"
});
In Loki, it will appear as:
[EVENT] usuario_ativo {"user_id": "abc123", "source": "mobile", "action": "login"}
Log Format in Loki
Each log sent includes:
Timestamp with nanosecond precision
Fixed and dynamic labels (e.g., app, env, trace_id, span_id)
Log type prefix (e.g.,
[INFO]
,[ERROR]
,[EVENT]
, etc)Content automatically serialized for objects and errors
Example of payload sent:
{
"streams": [
{
"stream": {
"app": "minha-api",
"environment": "production",
"level": "info",
"hostname": "api-01",
"trace_id": "abc123def456"
},
"values": [
["1640995200000000000", "[INFO] User successfully logged in {\\"userId\\": 123}"]
]
}
]
}
Integration with OpenTelemetry
This logger automatically integrates with OpenTelemetry, capturing the active trace and span IDs from the current context.
This enables seamless correlation between logs and traces in Grafana Tempo, allowing for complete end-to-end investigations.
What is already included:
trace_id
andspan_id
are automatically added as dynamic labels.No additional configuration is required, as long as the app already uses OpenTelemetry.
Compatible with any existing OpenTelemetry instrumentation.
Example with traces
import { context, trace } from "@opentelemetry/api";
import { init, logger } from "logs-interceptor";
// Initialize OpenTelemetry first:
const tracer = trace.getTracer("my-lib");
init({
transport: {
url: "<https://loki.elvenobservability.com/loki/api/v1/push>",
tenantId: "elven",
authToken: "your-token"
},
appName: "my-app",
labels: { env: "prod" },
});
tracer.startActiveSpan("processOrder", (span) => {
console.log("Processing order");
logger.info("Order started", { orderId: 12345 });
span.end();
});
In Loki:
Each log will automatically include:
{
"trace_id": "abcdef123456789",
"span_id": "12345678",
"app": "my-app",
"level": "info"
}
You will be able to query Loki by filtering with trace_id
, and with that, view the logs from the same trace shown in Grafana Tempo (or the trace view in the Elven Platform).
Docker & Kubernetes
Docker Compose
version: '3.8'
services:
app:
build: .
environment:
- NODE_OPTIONS=--require logs-interceptor/preload
- LOGS_INTERCEPTOR_URL=https://loki.elvenobservability.com/loki/api/v1/push
- LOGS_INTERCEPTOR_TENANT_ID=meu-tenant
- LOGS_INTERCEPTOR_AUTH_TOKEN=meu-token
- LOGS_INTERCEPTOR_APP_NAME=minha-api
- LOGS_INTERCEPTOR_ENVIRONMENT=production
Kubernetes
env:
- name: NODE_OPTIONS
value: "--require logs-interceptor/preload"
- name: LOGS_INTERCEPTOR_URL
value: "<https://loki.elvenobservability.com/loki/api/v1/push>"
- name: LOGS_INTERCEPTOR_TENANT_ID
valueFrom:
secretKeyRef:
name: elven-credentials
key: tenant-id
- name: LOGS_INTERCEPTOR_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: elven-credentials
key: auth-token
License
This project is licensed under the MIT License.
How to contribute
Fork this repository
Create a branch with your improvement
Submit a Pull Request
Any help is welcome to make the logger even more powerful!
Made with ❤️ by the Elven Observability team.
Last updated
Was this helpful?