@lognitor/node

The Node.js SDK for Lognitor. Works with any Node.js application — Express, Fastify, Hono, Next.js, NestJS, and more.


Installation

Terminal
npm install @lognitor/node

Quick Start

JavaScript
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:

JavaScript
// 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

JavaScript
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

OptionTypeDefaultDescription
apiKeystringRequired. Your project API key.
apiUrlstringhttps://api.lognitor.com/api/v1API endpoint.
servicestringService/app name shown in dashboard.
environmentstringEnvironment label (production, staging, etc).
versionstringApp version string.
batchSizenumber25Logs per batch.
flushIntervalnumber5000Auto-flush interval in milliseconds.
maxRetriesnumber3Retry count for failed requests.
maxQueueSizenumber1000Maximum logs held in memory. Oldest dropped when full.
minLevelstring | nullnullMinimum log level to send. null sends all.
enabledbooleantrueMaster switch. false drops all logs.
autoTruncatebooleanfalseTruncate oversized payloads instead of dropping.
maxBreadcrumbsnumber100Max breadcrumbs kept in ring buffer.
debugbooleanfalsePrint internal SDK diagnostics to console.
redactPatterns(string | RegExp)[][]Built-in: email, creditCard, ssn, bearer. Or custom RegExp.
scrubUrlParamsstring[]['token', 'password', ...]Query params to replace with [SCRUBBED] in URLs.
beforeSendfunction(log) => log to modify, or return null to drop.
transportTransportCustom transport for testing.
Plan limits

Check your plan's limits and ingestion quotas at dashboard.lognitor.com.

Log Levels

JavaScript
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 ''.

JavaScript
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:

JavaScript
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

JavaScript
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 user

Global Context, Tags, and Session

JavaScript
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_id

Error Capturing

JavaScript
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 record a trail of events leading up to an error. They are automatically attached to error and fatal level logs.

JavaScript
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

JavaScript
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_ms

Child Loggers

Child loggers inherit the parent's transport and buffer but have their own service name, metadata, and tags.

JavaScript
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');
Info

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.

JavaScript
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

JavaScript
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

JavaScript
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 context

Framework Integrations

Express

JavaScript
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

JavaScript
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

JavaScript
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

JavaScript
// 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

JavaScript
// 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 {}
What the middleware captures

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

JavaScript
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

JavaScript
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.

JavaScript
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

JavaScript
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

JavaScript
import { pause, resume } from '@lognitor/node';

pause();   // Stops sending logs (still buffers them)
resume();  // Resumes sending and flushes buffer

Reconfigure at Runtime

JavaScript
import { reconfigure } from '@lognitor/node';

reconfigure({
  minLevel: 'warn',
  enabled: false,
  batchSize: 50,
  debug: true,
});

Custom Transport (Testing)

JavaScript
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

JavaScript
import { addIntegration } from '@lognitor/node';

addIntegration({
  name: 'my-plugin',
  setup(client) {
console.log('Plugin initialized');
  },
  teardown() {
console.log('Plugin cleaned up');
  },
});