@lognitor/node
The Node.js SDK for Lognitor. Works with any Node.js application — Express, Fastify, Hono, Next.js, NestJS, and more.
Installation
npm install @lognitor/nodeQuick Start
import Lognitor from '@lognitor/node';
Lognitor.init({
apiKey: 'your-api-key',
service: 'my-api',
environment: 'production',
version: '1.2.0',
});
Lognitor.info('Server started on port 3000');
Lognitor.error('Database connection failed', {
error: new Error('ECONNREFUSED'),
metadata: { host: 'db.internal', port: 5432 },
});Alternative import styles:
// Namespace import (same behavior as default)
import * as Lognitor from '@lognitor/node';
Lognitor.init({ apiKey: 'your-key' });
Lognitor.info('hello');
// Destructured import (shorter, no prefix)
import { init, info, error, setUser, flush } from '@lognitor/node';
init({ apiKey: 'your-key' });
info('hello');All three styles use the same singleton instance. Choose whichever fits your codebase.
Configuration
import Lognitor from '@lognitor/node';
Lognitor.init({
// Required
apiKey: 'your-api-key',
// Identity
service: 'payment-service',
environment: 'production',
version: '2.1.0',
// Batching
batchSize: 25,
flushInterval: 5000,
maxQueueSize: 1000,
// Retry
maxRetries: 3,
// Filtering
minLevel: 'info',
enabled: true,
// Privacy
redactPatterns: ['email', 'creditCard', 'ssn', 'bearer'],
scrubUrlParams: ['token', 'password', 'secret', 'authorization'],
// Advanced
autoTruncate: true,
maxBreadcrumbs: 100,
debug: false,
beforeSend: (log) => {
if (log.message.includes('healthcheck')) return null;
return log;
},
});Configuration Options Reference
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Required. Your project API key. |
apiUrl | string | https://api.lognitor.com/api/v1 | API endpoint. |
service | string | — | Service/app name shown in dashboard. |
environment | string | — | Environment label (production, staging, etc). |
version | string | — | App version string. |
batchSize | number | 25 | Logs per batch. |
flushInterval | number | 5000 | Auto-flush interval in milliseconds. |
maxRetries | number | 3 | Retry count for failed requests. |
maxQueueSize | number | 1000 | Maximum logs held in memory. Oldest dropped when full. |
minLevel | string | null | null | Minimum log level to send. null sends all. |
enabled | boolean | true | Master switch. false drops all logs. |
autoTruncate | boolean | false | Truncate oversized payloads instead of dropping. |
maxBreadcrumbs | number | 100 | Max breadcrumbs kept in ring buffer. |
debug | boolean | false | Print internal SDK diagnostics to console. |
redactPatterns | (string | RegExp)[] | [] | Built-in: email, creditCard, ssn, bearer. Or custom RegExp. |
scrubUrlParams | string[] | ['token', 'password', ...] | Query params to replace with [SCRUBBED] in URLs. |
beforeSend | function | — | (log) => log to modify, or return null to drop. |
transport | Transport | — | Custom transport for testing. |
Check your plan's limits and ingestion quotas at dashboard.lognitor.com.
Log Levels
import { debug, info, warn, error, fatal, log } from '@lognitor/node';
debug('Cache miss for key user:123');
info('Order created', { metadata: { orderId: 'ord_456' } });
warn('Rate limit approaching', { metadata: { usage: 850, limit: 1000 } });
error('Payment failed', { error: new Error('Card declined') });
fatal('Database corrupted, shutting down');
// Or use log() with explicit level
log('info', 'Custom level call');Every log call returns a unique string ID. If the log was dropped (by beforeSend, minLevel, or enabled: false), it returns ''.
const logId = info('Order shipped', { metadata: { orderId: 'ord_789' } });
console.log(`Log ID: ${logId}`); // e.g. "a1b2c3d4-..."Per-Log Options
Every log call accepts an options object as the second argument:
info('User signed up', {
metadata: { plan: 'pro', referrer: 'google' },
tags: ['signup', 'marketing'],
user: { id: 'user_123', email: 'alice@example.com', name: 'Alice' },
request: {
method: 'POST', url: '/api/users', path: '/api/users',
status_code: 201, duration_ms: 45, ip: '203.0.113.1',
user_agent: 'Mozilla/5.0...',
},
perf: { duration_ms: 120, memory_mb: 256 },
trace: { trace_id: 'abc123', span_id: 'def456', parent_span_id: 'ghi789' },
deploy: { commit: 'a1b2c3d', branch: 'main', deployed_by: 'ci' },
action: 'user.signup',
request_id: 'req_abc123',
session_id: 'sess_xyz',
release_id: 'rel_v2',
source: 'auth-service.ts:42',
notify: true,
notifyChannels: ['slack', 'email'],
});User Context
import { setUser, clearUser } from '@lognitor/node';
// Set user — attached to all subsequent logs
setUser({ id: 'user_123', email: 'alice@example.com', name: 'Alice' });
info('Profile updated'); // This log includes user_id: 'user_123'
// Override user for a single log
info('Admin action', { user: { id: 'admin_1', name: 'Admin' } });
// Clear user (e.g. on logout)
clearUser();
// setUser(null) also clears the userGlobal Context, Tags, and Session
import { setContext, setTags, setSession } from '@lognitor/node';
// Context is merged (not replaced) on each call
setContext({ region: 'us-east-1' });
setContext({ deploy_id: 'deploy_456' }); // Now has both
// Tags are replaced on each call
setTags(['production', 'critical-path']);
// Session ID for grouping logs
setSession('sess_abc123');
info('Request processed'); // includes context, tags, session_idError Capturing
import { captureException, error } from '@lognitor/node';
// Method 1: captureException
try {
await processPayment(order);
} catch (err) {
captureException(err, {
metadata: { orderId: order.id, amount: order.total },
tags: ['payment', 'critical'],
request_id: 'req_123',
});
}
// Method 2: error() with error context
error('Payment processing failed', {
error: new Error('Card declined'),
metadata: { gateway: 'stripe', cardLast4: '4242' },
});
// Method 3: error() with a plain error object
error('External service error', {
error: { type: 'TimeoutError', message: 'Request timed out after 30s', stack: '' },
});The SDK automatically generates a fingerprint for errors (based on error type and stack trace) and deduplicates identical errors. If the same error fires 100 times in 5 seconds, only the first is sent immediately — the rest are counted and a summary log is sent with the total count.
Breadcrumbs
Breadcrumbs record a trail of events leading up to an error. They are automatically attached to error and fatal level logs.
import { addBreadcrumb } from '@lognitor/node';
addBreadcrumb({
type: 'http',
category: 'api',
message: 'GET /api/users 200',
level: 'info',
data: { duration_ms: 45 },
});
addBreadcrumb({
type: 'db',
category: 'query',
message: 'SELECT * FROM orders WHERE id = ?',
level: 'info',
data: { rows: 1, duration_ms: 12 },
});
// When an error occurs, breadcrumbs are automatically attached
error('Failed to process order', { error: new Error('Insufficient stock') });Breadcrumbs are stored in a ring buffer. When the buffer is full (default: 100), the oldest breadcrumbs are dropped.
Timers
import { startTimer } from '@lognitor/node';
const timer = startTimer();
await heavyComputation();
timer.end('Computation finished', {
metadata: { input_size: 1000 },
perf: { db_queries: 5, cache_hits: 12 },
});
// Automatically includes perf.duration_msChild Loggers
Child loggers inherit the parent's transport and buffer but have their own service name, metadata, and tags.
import { init, child } from '@lognitor/node';
const client = init({ apiKey: 'your-key', service: 'main-app' });
const paymentLogger = child({
service: 'payment-module',
metadata: { module: 'payment' },
tags: ['payments'],
});
paymentLogger.info('Processing payment');
// Grandchild
const stripeLogger = paymentLogger.child({ service: 'stripe-adapter' });
stripeLogger.info('Charge created');Child's tags replace the parent's tags (not merge). Child's metadata merges with the parent's context.
Heartbeat Monitoring
Heartbeat monitors track cron jobs, scheduled tasks, and background processes. Create a monitor in the dashboard and use the token.
import { heartbeat } from '@lognitor/node';
const hb = heartbeat('your-monitor-token');
// Simple ping
await hb.ping();
// Wrap a job — pings on success, captures exception on failure
const result = await hb.wrap(async () => {
await syncInventory();
return { synced: 150 };
});User Feedback
import { submitFeedback } from '@lognitor/node';
await submitFeedback({
eventId: logId,
comments: 'The page crashed when I clicked submit',
name: 'Alice',
email: 'alice@acme.com',
});Release Tracking
import { registerRelease } from '@lognitor/node';
const release = await registerRelease({
version: '2.1.0',
commitHash: 'a1b2c3d4e5f6',
branch: 'main',
deployedBy: 'github-actions',
});
console.log(release.release_id);
// All subsequent logs include release_id and deploy contextFramework Integrations
Express
import express from 'express';
import { init } from '@lognitor/node';
import { createExpressMiddleware } from '@lognitor/node/integrations/express';
const client = init({
apiKey: 'your-key',
service: 'my-express-api',
environment: 'production',
});
const app = express();
app.use(createExpressMiddleware(client, {
ignoreRoutes: ['/health', '/ready', '/metrics'],
}));
app.get('/api/users', (req, res) => {
client.info('Fetching users');
res.json({ users: [] });
});
app.listen(3000);Fastify
import Fastify from 'fastify';
import { init } from '@lognitor/node';
import { createFastifyPlugin } from '@lognitor/node/integrations/fastify';
const client = init({ apiKey: 'your-key', service: 'my-fastify-api' });
const fastify = Fastify();
fastify.register(createFastifyPlugin(client, {
ignoreRoutes: ['/health'],
}));
fastify.get('/api/users', async () => {
return { users: [] };
});
fastify.listen({ port: 3000 });Hono
import { Hono } from 'hono';
import { init } from '@lognitor/node';
import { createHonoMiddleware } from '@lognitor/node/integrations/hono';
const client = init({ apiKey: 'your-key', service: 'my-hono-api' });
const app = new Hono();
app.use('*', createHonoMiddleware(client));
app.get('/api/users', (c) => c.json({ users: [] }));
export default app;Next.js
// app/api/users/route.ts
import { init } from '@lognitor/node';
import { createNextjsWrapper } from '@lognitor/node/integrations/nextjs';
const client = init({ apiKey: 'your-key', service: 'my-nextjs-app' });
const withLognitor = createNextjsWrapper(client);
export const GET = withLognitor(async (req) => {
client.info('Fetching users');
return Response.json({ users: [] });
});
export const POST = withLognitor(async (req) => {
const body = await req.json();
return Response.json({ created: true });
});NestJS
// app.module.ts
import { Module } from '@nestjs/common';
import { LognitorModule, LognitorInterceptor } from '@lognitor/node/integrations/nestjs';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
imports: [
LognitorModule.forRoot({
apiKey: 'your-key',
service: 'my-nestjs-api',
environment: 'production',
}),
],
providers: [
{
provide: APP_INTERCEPTOR,
useFactory: (client) => new LognitorInterceptor(client),
inject: ['LOGNITOR_CLIENT'],
},
],
})
export class AppModule {}Each framework integration automatically sends structured request data (method, path, status code, duration, IP, user agent, route) to the /ingest/requests endpoint for request analytics. It also captures unhandled exceptions and adds HTTP breadcrumbs for error context.
PII Redaction
Built-in Patterns
Lognitor.init({
apiKey: 'your-key',
redactPatterns: ['email', 'creditCard', 'ssn', 'bearer'],
});
info('User alice@example.com signed up with card 4111-1111-1111-1111');
// Sent as: "User [REDACTED] signed up with card [REDACTED]"Custom Patterns
Lognitor.init({
apiKey: 'your-key',
redactPatterns: [
'email',
/\bAPI-[A-Z0-9]{16,}\b/g, // Custom API key pattern
/\b\d{3}-\d{3}-\d{4}\b/g, // US phone numbers
],
});Redaction applies to the log message and all string values in metadata. User context (user.email) is preserved and not redacted.
URL Scrubbing
Sensitive query parameters are automatically replaced with [SCRUBBED] in request.url, request.path, and breadcrumb URLs.
Lognitor.init({
apiKey: 'your-key',
scrubUrlParams: ['token', 'password', 'secret', 'authorization', 'session_id'],
});
info('Request received', {
request: { url: '/api/auth?token=sk_live_abc123&page=1' },
});
// request.url sent as: "/api/auth?token=[SCRUBBED]&page=1"Flush and Shutdown
import { flush, shutdown } from '@lognitor/node';
// Manually flush all buffered logs (waits for completion)
await flush();
// Graceful shutdown — flushes all logs, dedup summaries, and cleans up timers
await shutdown();
// The SDK also installs SIGTERM/SIGINT handlers that flush before exit.
// In serverless environments, call flush() at the end of each invocation.Pause and Resume
import { pause, resume } from '@lognitor/node';
pause(); // Stops sending logs (still buffers them)
resume(); // Resumes sending and flushes bufferReconfigure at Runtime
import { reconfigure } from '@lognitor/node';
reconfigure({
minLevel: 'warn',
enabled: false,
batchSize: 50,
debug: true,
});Custom Transport (Testing)
import { Lognitor, MemoryTransport } from '@lognitor/node';
const transport = new MemoryTransport();
const client = new Lognitor({ apiKey: 'test', transport });
client.info('test message');
await client.flush();
console.log(transport.logs); // Array of log payloads
console.log(transport.requests); // Array of { url, payload, headers }Plugins
import { addIntegration } from '@lognitor/node';
addIntegration({
name: 'my-plugin',
setup(client) {
console.log('Plugin initialized');
},
teardown() {
console.log('Plugin cleaned up');
},
});