Official Node.js SDK for LogWard with advanced features: retry logic, circuit breaker, query API, live streaming, and middleware support.
- âś… Automatic batching with configurable size and interval
- âś… Retry logic with exponential backoff
- âś… Circuit breaker pattern for fault tolerance
- âś… Max buffer size with drop policy to prevent memory leaks
- âś… Query API for searching and filtering logs
- âś… Live tail with Server-Sent Events (SSE)
- âś… Trace ID context for distributed tracing
- âś… Global metadata added to all logs
- âś… Structured error serialization
- âś… Internal metrics (logs sent, errors, latency, etc.)
- âś… Express & Fastify middleware for auto-logging HTTP requests
- âś… Full TypeScript support with strict types
npm install @logward-dev/sdk-node
# or
pnpm add @logward-dev/sdk-node
# or
yarn add @logward-dev/sdk-nodeimport { LogWardClient } from '@logward-dev/sdk-node';
const client = new LogWardClient({
apiUrl: 'http://localhost:8080',
apiKey: 'lp_your_api_key_here',
});
// Send logs
client.info('api-gateway', 'Server started', { port: 3000 });
client.error('database', 'Connection failed', new Error('Timeout'));
// Graceful shutdown
process.on('SIGINT', async () => {
await client.close();
process.exit(0);
});| Option | Type | Default | Description |
|---|---|---|---|
apiUrl |
string |
required | Base URL of your LogWard instance |
apiKey |
string |
required | Project API key (starts with lp_) |
batchSize |
number |
100 |
Number of logs to batch before sending |
flushInterval |
number |
5000 |
Interval in ms to auto-flush logs |
| Option | Type | Default | Description |
|---|---|---|---|
maxBufferSize |
number |
10000 |
Max logs in buffer (prevents memory leak) |
maxRetries |
number |
3 |
Max retry attempts on failure |
retryDelayMs |
number |
1000 |
Initial retry delay (exponential backoff) |
circuitBreakerThreshold |
number |
5 |
Failures before opening circuit |
circuitBreakerResetMs |
number |
30000 |
Time before retrying after circuit opens |
enableMetrics |
boolean |
true |
Track internal metrics |
debug |
boolean |
false |
Enable debug logging to console |
globalMetadata |
object |
{} |
Metadata added to all logs |
autoTraceId |
boolean |
false |
Auto-generate trace IDs for logs |
const client = new LogWardClient({
apiUrl: 'http://localhost:8080',
apiKey: 'lp_your_api_key_here',
// Batching
batchSize: 100,
flushInterval: 5000,
// Buffer management
maxBufferSize: 10000,
// Retry with exponential backoff (1s → 2s → 4s)
maxRetries: 3,
retryDelayMs: 1000,
// Circuit breaker
circuitBreakerThreshold: 5,
circuitBreakerResetMs: 30000,
// Metrics & debugging
enableMetrics: true,
debug: true,
// Global context
globalMetadata: {
env: process.env.NODE_ENV,
version: '1.0.0',
hostname: process.env.HOSTNAME,
},
// Auto trace IDs
autoTraceId: false,
});client.debug('service-name', 'Debug message');
client.info('service-name', 'Info message', { userId: 123 });
client.warn('service-name', 'Warning message');
client.error('service-name', 'Error message', { custom: 'data' });
client.critical('service-name', 'Critical message');The SDK automatically serializes Error objects:
try {
throw new Error('Database timeout');
} catch (error) {
// Automatically serializes error with stack trace
client.error('database', 'Query failed', error);
}Generated log metadata:
{
"error": {
"name": "Error",
"message": "Database timeout",
"stack": "Error: Database timeout\n at ..."
}
}client.log({
service: 'custom-service',
level: 'info',
message: 'Custom log',
time: new Date().toISOString(), // Optional
metadata: { key: 'value' },
trace_id: 'custom-trace-id', // Optional
});Track requests across services with trace IDs.
client.setTraceId('request-123');
client.info('api', 'Request received');
client.info('database', 'Querying users');
client.info('api', 'Response sent');
client.setTraceId(null); // Clear contextclient.withTraceId('request-456', () => {
client.info('api', 'Processing in context');
client.warn('cache', 'Cache miss');
});
// Trace ID automatically restored after blockclient.withNewTraceId(() => {
client.info('worker', 'Background job started');
client.info('worker', 'Job completed');
});const client = new LogWardClient({
apiUrl: 'http://localhost:8080',
apiKey: 'lp_your_api_key_here',
autoTraceId: true, // Every log gets a unique trace ID
});Search and retrieve logs programmatically.
const result = await client.query({
service: 'api-gateway',
level: 'error',
from: new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24h
to: new Date(),
limit: 100,
offset: 0,
});
console.log(`Found ${result.total} logs`);
console.log(result.logs);const result = await client.query({
q: 'timeout',
limit: 50,
});const logs = await client.getByTraceId('trace-123');
console.log(`Trace has ${logs.length} logs`);const stats = await client.getAggregatedStats({
from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Last 7 days
to: new Date(),
interval: '1h', // '1m' | '5m' | '1h' | '1d'
service: 'api-gateway', // Optional
});
console.log(stats.timeseries); // Time-bucketed counts
console.log(stats.top_services); // Top services by log count
console.log(stats.top_errors); // Most common errorsStream logs in real-time using Server-Sent Events.
const cleanup = client.stream({
service: 'api-gateway', // Optional filter
level: 'error', // Optional filter
onLog: (log) => {
console.log(`[${log.time}] ${log.level}: ${log.message}`);
},
onError: (error) => {
console.error('Stream error:', error);
},
});
// Stop streaming when done
setTimeout(() => {
cleanup();
}, 60000);Track SDK performance and health.
const metrics = client.getMetrics();
console.log(metrics.logsSent); // Total logs sent
console.log(metrics.logsDropped); // Logs dropped (buffer full)
console.log(metrics.errors); // Send errors
console.log(metrics.retries); // Retry attempts
console.log(metrics.avgLatencyMs); // Average send latency
console.log(metrics.circuitBreakerTrips); // Circuit breaker openings
// Get circuit breaker state
console.log(client.getCircuitBreakerState()); // 'CLOSED' | 'OPEN' | 'HALF_OPEN'
// Reset metrics
client.resetMetrics();Auto-log all HTTP requests and responses.
import express from 'express';
import { LogWardClient, logWardMiddleware } from '@logward-dev/sdk-node';
const app = express();
const logger = new LogWardClient({
apiUrl: 'http://localhost:8080',
apiKey: 'lp_your_api_key_here',
});
app.use(
logWardMiddleware({
client: logger,
serviceName: 'express-api',
logRequests: true,
logResponses: true,
logErrors: true,
includeHeaders: false,
includeBody: false,
skipHealthCheck: true,
skipPaths: ['/metrics'],
}),
);
app.get('/', (req, res) => {
res.json({ message: 'Hello' });
});
app.listen(3000);Logged automatically:
- Request:
GET /users/123 - Response:
GET /users/123 200 (45ms) - Errors:
Request error: Internal Server Error
import Fastify from 'fastify';
import { LogWardClient, logWardFastifyPlugin } from '@logward-dev/sdk-node';
const fastify = Fastify();
const logger = new LogWardClient({
apiUrl: 'http://localhost:8080',
apiKey: 'lp_your_api_key_here',
});
await fastify.register(logWardFastifyPlugin, {
client: logger,
serviceName: 'fastify-api',
logRequests: true,
logResponses: true,
logErrors: true,
skipHealthCheck: true,
});
fastify.get('/', async () => ({ message: 'Hello' }));
await fastify.listen({ port: 3000 });process.on('SIGINT', async () => {
await client.close(); // Flushes buffered logs
process.exit(0);
});
process.on('SIGTERM', async () => {
await client.close();
process.exit(0);
});const client = new LogWardClient({
apiUrl: 'http://localhost:8080',
apiKey: 'lp_your_api_key_here',
globalMetadata: {
env: process.env.NODE_ENV,
version: require('./package.json').version,
region: process.env.AWS_REGION,
pod: process.env.HOSTNAME,
},
});const client = new LogWardClient({
apiUrl: 'http://localhost:8080',
apiKey: 'lp_your_api_key_here',
debug: process.env.NODE_ENV === 'development',
});setInterval(() => {
const metrics = client.getMetrics();
if (metrics.logsDropped > 0) {
console.warn(`Logs dropped: ${metrics.logsDropped}`);
}
if (metrics.circuitBreakerTrips > 0) {
console.error('Circuit breaker is OPEN!');
}
}, 60000);app.use((req, res, next) => {
const traceId = req.headers['x-trace-id'] || randomUUID();
req.traceId = traceId;
client.withTraceId(traceId, () => {
next();
});
});new LogWardClient(options: LogWardClientOptions)log(entry: LogEntry): voiddebug(service: string, message: string, metadata?: object): voidinfo(service: string, message: string, metadata?: object): voidwarn(service: string, message: string, metadata?: object): voiderror(service: string, message: string, metadataOrError?: object | Error): voidcritical(service: string, message: string, metadataOrError?: object | Error): void
setTraceId(traceId: string | null): voidgetTraceId(): string | nullwithTraceId<T>(traceId: string, fn: () => T): TwithNewTraceId<T>(fn: () => T): T
query(options: QueryOptions): Promise<LogsResponse>getByTraceId(traceId: string): Promise<InternalLogEntry[]>getAggregatedStats(options: AggregatedStatsOptions): Promise<AggregatedStatsResponse>
stream(options: StreamOptions): () => void(returns cleanup function)
getMetrics(): ClientMetricsresetMetrics(): voidgetCircuitBreakerState(): string
flush(): Promise<void>close(): Promise<void>
See the examples/ directory for complete working examples:
- basic.ts - Simple usage
- advanced.ts - All advanced features
- express-middleware.ts - Express integration
- fastify-plugin.ts - Fastify integration
Fully typed with strict TypeScript support:
import type {
LogWardClient,
LogEntry,
QueryOptions,
LogsResponse,
ClientMetrics,
} from '@logward-dev/sdk-node';Run the test suite:
# Run tests
pnpm test
# Watch mode
pnpm test:watch
# Coverage
pnpm test:coverageMIT
Contributions are welcome! Please open an issue or PR on GitHub.
- Documentation: https://logward.dev/docs
- Issues: GitHub Issues