@nlite/logger-core
v1.0.2
Published
> The framework-agnostic core SDK that powers every NLite Logger client. It owns the log pipeline (queue, batching, retry, breadcrumbs, sessions, users) and exposes a `Transport` interface so each framework-specific SDK can ship logs over its preferred wi
Maintainers
Readme
@nlite/logger-core
The framework-agnostic core SDK that powers every NLite Logger client. It owns the log pipeline (queue, batching, retry, breadcrumbs, sessions, users) and exposes a
Transportinterface so each framework-specific SDK can ship logs over its preferred wire (HTTP, native modules, etc.).
Part of the NLite Logger monorepo. See the root README for the full architecture.
Table of Contents
- Why
@nlite/logger-core? - Installation
- Quick Start
- Concepts
- API Reference
- Transports
- Log Pipeline & Workflow
- Architecture Diagrams
- Examples
- Configuration Reference
- Testing
- Building
- Contributing
- License & Author
Why @nlite/logger-core?
Every NLite SDK (@nlite/logger-hapi, @nlite/logger-react-native, @nlite/logger-vue, …) shares the same logging semantics. To avoid duplicating that logic we extracted it into a single framework-agnostic package:
- One queue, one retry policy, one breadcrumb ring for every platform.
- Pluggable transports — pass any object that satisfies
Transport. - Strict TypeScript types so every SDK ships the same surface (
logger.info,logger.captureException,logger.addBreadcrumb, …). - Schema validation built on top of Zod for safe ingestion.
You usually do not install this package directly. Instead, install the SDK for your framework, which depends on @nlite/logger-core automatically. Install it directly only when:
- You are building a new SDK on top of NLite.
- You want to send logs from a custom Node.js service without picking a framework SDK.
- You want full control over the
Transportimplementation.
Installation
# npm
npm install @nlite/logger-core
# pnpm
pnpm add @nlite/logger-core
# yarn
yarn add @nlite/logger-corePeer / runtime requirements
| Tool | Version |
|------|---------|
| Node.js | >=18.0.0 |
| TypeScript (optional, recommended) | >=5.3.3 |
| zod | ^3.22.4 (bundled as a dependency) |
Quick Start
import {
createLogger,
FetchTransport,
} from '@nlite/logger-core';
const logger = createLogger(
{
apiKey: process.env.NLITE_API_KEY!,
endpoint: 'http://localhost:3000',
appName: 'order-service',
appVersion: '1.4.0',
environment: 'production',
platform: 'backend',
autoCapture: true,
batchSize: 25,
flushInterval: 5_000,
},
new FetchTransport('http://localhost:3000', process.env.NLITE_API_KEY!)
);
// Use it like any logger
logger.info('Order created', { orderId: 'o_123', amount: 49.99 });
logger.error('Payment failed', new Error('Card declined'), { orderId: 'o_123' });
// Make sure pending logs are flushed before exit
process.on('SIGTERM', () => logger.destroy());That is enough to start shipping structured logs to a self-hosted NLite server or to the hosted SaaS endpoint.
Concepts
Log levels
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';Log categories
type LogCategory =
| 'application'
| 'http'
| 'navigation'
| 'lifecycle'
| 'crash'
| 'resource'
| 'network'
| 'custom';Breadcrumbs
A breadcrumb is a short, contextual hint attached to subsequent logs (last 10 are kept, last 100 are stored in memory).
logger.addBreadcrumb({
type: 'navigation',
category: 'route',
message: 'home -> checkout',
level: 'info',
data: { from: '/', to: '/checkout' },
});Sessions & users
logger.setUser('user_42', { plan: 'pro', country: 'IN' });
const sessionId = logger.startSession({ device: 'iphone-15' });beforeSend hook
Drop or transform any log before it is enqueued.
createLogger(
{
/* ... */
beforeSend: (log) => {
if (log.message.includes('IGNORE_ME')) return null; // drop
return log; // send as-is
},
},
transport
);API Reference
createLogger(config, transport): LoggerSdk
Creates a new logger instance. Required config fields are apiKey, appName, platform; everything else has a sensible default.
LoggerSdk methods
| Method | Description |
|--------|-------------|
| trace/debug/info/warn/error/fatal(message, context?) | Shorthand log methods. error and fatal may receive an Error as the second argument. |
| log(level, message, context?) | Dynamic-level log method. |
| setUser(id, data?) | Bind a user to all subsequent logs. |
| setTags(tags) | Merge global tags onto every log. |
| startSession(tags?) | Begin a new session, returns the session id. |
| getSession() | Returns the current session context. |
| addBreadcrumb(breadcrumb) | Append a breadcrumb (keeps the latest 100). |
| child({ userId?, sessionId?, tags? }) | Create a derived logger sharing the underlying queue. |
| getConfig() | Snapshot of the resolved configuration. |
| isInitialized() | true once the SDK is ready. |
| flush(): Promise<void> | Force a flush of the pending queue. |
| destroy(): Promise<void> | Cancel timers, flush, close the transport. |
Types
import type {
SdkConfig,
LoggerSdk,
LogLevel,
LogCategory,
LogContext,
LogMetadata,
LogError,
LogRequest,
LogResponse,
IngestLogRequest,
IngestBatchLogsRequest,
Breadcrumb,
Transport,
UserContext,
SessionContext,
Environment,
Platform,
LogMethod,
} from '@nlite/logger-core';Transports
A Transport is anything that satisfies:
interface Transport {
send(logs: IngestLogRequest[]): Promise<void>;
close(): Promise<void>;
}The package ships FetchTransport, which POSTs to ${endpoint}/api/logs/batch using the browser/Node 18+ fetch. Implement your own to send logs over gRPC, a message queue, native modules, etc.
class KafkaTransport implements Transport {
constructor(private producer: Producer, private topic: string) {}
async send(logs: IngestLogRequest[]): Promise<void> {
await this.producer.send({ topic: this.topic, messages: logs.map((l) => ({ value: JSON.stringify(l) })) });
}
async close(): Promise<void> {
await this.producer.disconnect();
}
}Log Pipeline & Workflow
The same flow runs in every SDK. Understanding it helps you reason about retries, batching, and beforeSend.
┌──────────────────────────────────────────────┐
│ Application Code │
│ logger.info(msg, ctx) logger.error(err,ctx) │
└────────────────────┬─────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ 1. buildLogEntry │
│ - attach timestamp, source, sdk version │
│ - merge user / session context │
│ - attach last 10 breadcrumbs │
│ - serialize Error → {name, message, stack, code, cause} │
└────────────────────┬──────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ 2. beforeSend(log) │
│ - return null → drop │
│ - return log → continue (transform allowed) │
└────────────────────┬──────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ 3. enqueue │
│ - bounded queue (maxQueueSize, default 1000) │
│ - drops oldest non-error first when full │
│ - flushes immediately on error/fatal │
│ - flushes when queue ≥ batchSize (default 10) │
└────────────────────┬──────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ 4. flush timer (every flushInterval ms, default 5000) │
│ - pops batchSize items │
│ - sendWithRetry (maxAttempts, exponential backoff) │
│ - on success → ack │
│ - on failure → re-queue with retry counter │
└────────────────────┬──────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ 5. Transport.send │
│ - FetchTransport: POST {endpoint}/api/logs/batch │
│ - Custom transports: Kafka, gRPC, AsyncStorage, ... │
└────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ @nlite/logger-server │
│ /api/logs/batch → SQLite │
│ Redis pub/sub → WS clients │
└─────────────────────────────┘Lifecycle
- Construct —
createLoggervalidates config, builds the queue, generates a session id. - Auto-capture — when
autoCapture: true,globalThis.onerrorandglobalThis.onunhandledrejectionare wrapped. - Run — every log call goes through
buildLogEntry → beforeSend → enqueue. - Flush — triggered by the timer, by
batchSize, by anerror/fatal, by an explicitflush(), or bydestroy(). - Destroy — clears the timer, performs a final flush, calls
transport.close().
Architecture Diagrams
Component view
+----------------------+ +-------------------------+
| Your Application | uses | @nlite/logger-core |
| (Hapi / RN / Vue / …) +------->+ createLogger(config, |
+----------------------+ | transport) |
+-----------+-------------+
|
v
+-------------+--------------+
| In-memory bounded queue |
| - retry/backoff |
| - breadcrumbs ring (100) |
+-------------+--------------+
|
v
+-------------+--------------+
| Transport (interface) |
| FetchTransport (default) |
| or your custom transport |
+-------------+--------------+
|
v
POST /api/logs/batch
NLite Logger serverSequence diagram — successful log
App CoreLogger Transport Server
| | | |
| info() | | |
|----------->| | |
| | buildEntry | |
| | beforeSend | |
| | enqueue | |
| | | |
| …later… | | |
| | flush (timer) | |
| |--------------> | |
| | | POST batch |
| | |------------>|
| | | 200 OK |
| | |<------------|
| | ack | |Sequence diagram — failed batch with retry
App CoreLogger Transport Server
| | | |
| | flush | |
| |--------------> | |
| | | POST batch |
| | |------------>|
| | | 503 |
| | |<------------|
| | retry #1 (1s) | |
| |--------------> | |
| | | POST batch |
| | |------------>|
| | | 200 OK |
| | |<------------|
| | ack | |Examples
Express-style request wrapper (no SDK required)
import express from 'express';
import { createLogger, FetchTransport } from '@nlite/logger-core';
const logger = createLogger(
{
apiKey: process.env.NLITE_API_KEY!,
endpoint: 'http://localhost:3000',
appName: 'checkout',
platform: 'backend',
},
new FetchTransport('http://localhost:3000', process.env.NLITE_API_KEY!)
);
const app = express();
app.use((req, _res, next) => {
logger.addBreadcrumb({
type: 'http',
category: 'request',
message: `${req.method} ${req.url}`,
level: 'info',
data: { method: req.method, url: req.url },
});
next();
});
app.get('/orders/:id', async (req, res) => {
try {
const order = await loadOrder(req.params.id);
logger.info('Order loaded', { orderId: order.id, tags: ['order', 'success'] });
res.json(order);
} catch (err) {
logger.error('Failed to load order', err as Error, { orderId: req.params.id });
res.status(500).json({ error: 'internal' });
}
});Child loggers
const base = createLogger({ /* ... */ }, transport);
const worker = base.child({ tags: { worker: 'invoice-pdf' } });
worker.info('Started job');
worker.error('Job failed', new Error('OOM'));PII redaction with beforeSend
createLogger(
{
apiKey,
appName: 'payments',
platform: 'backend',
endpoint: 'http://localhost:3000',
beforeSend: (log) => ({
...log,
message: log.message.replace(/\b\d{16}\b/g, '[REDACTED_CARD]'),
context: {
...log.context,
email: log.context?.email ? '[REDACTED_EMAIL]' : undefined,
},
}),
},
new FetchTransport('http://localhost:3000', apiKey)
);Configuration Reference
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| apiKey | string | — | Required. Server-issued API key (NLITE_API_KEY). |
| appName | string | — | Required. Logical name of the application (e.g. checkout). |
| platform | Platform | — | Required. One of backend, browser, node, react-native, android, ios, vue, custom. |
| endpoint | string | http://localhost:3000 | URL of the NLite server. |
| appVersion | string | 1.0.0 | Release identifier. |
| environment | Environment | development | development, staging, production, test. |
| autoCapture | boolean | true | Wrap onerror / onunhandledrejection. |
| batchSize | number | 10 | Maximum logs per batch. |
| flushInterval | number | 5000 | Milliseconds between automatic flushes. |
| maxQueueSize | number | 1000 | Hard cap on the in-memory queue. |
| enableConsole | boolean | true | Mirror logs to console.*. |
| beforeSend | function \| null | null | Hook described above. |
| tags | Record<string, string> | {} | Tags attached to every log. |
| headers | Record<string, string> | {} | Extra headers sent with every batch. |
| timeout | number | 10000 | HTTP timeout (ms) per batch. |
| retry.maxAttempts | number | 3 | Retries per batch. |
| retry.delayMs | number | 1000 | Initial backoff delay. |
| retry.backoffMultiplier | number | 2 | Exponential backoff multiplier. |
Testing
npm test # one-shot
npm run test:watch # watch modeTests live in src/__tests__/ and use Vitest.
Building
npm run build # emit to dist/
npm run dev # tsc --watch
npm run typecheck # tsc --noEmit
npm run lint # eslint src --ext .tsprepublishOnly runs npm run build automatically.
Contributing
- Fork & branch from
main. - Add tests under
src/__tests__/. - Keep the public API stable; new exports go through
src/index.ts. - Run
npm run lint && npm run typecheck && npm testbefore pushing.
License & Author
MIT — © Debanjan Dasgupta. See the root README for the full project license.
