LogLayer
A structured logging library with a fluent API for specifying log messages, metadata and errors
LogLayer
A structured logging library with a fluent API for Typescript / Javascript. It separates log data into context (persistent), metadata (per-message), and errors with support for 25+ transports and plugins.
Installation
npm install loglayer
Quick Start
import { LogLayer, ConsoleTransport } from 'loglayer'
import type { ILogLayer } from 'loglayer'
const log: ILogLayer = new LogLayer({
transport: new ConsoleTransport({
logger: console,
}),
})
log.info('Hello world!')
Log Levels
log.trace('Detailed debugging')
log.debug('Debug information')
log.info('Informational message')
log.warn('Warning message')
log.error('Error occurred')
log.fatal('Critical failure')
// Multiple parameters
log.info('User', 123, 'logged in')
// Tagged template syntax (use withMetadata for structured data)
log.info`User ${userId} logged in`
log.warn`Request ${requestId} took ${duration}ms`
Tagged Template Syntax
Log methods accept tagged templates directly — no special setup required.
const userId = '123'
const count = 42
// Simple interpolations work great
log.info`User ${userId} logged in`
// Multiple values
log.warn`Request ${requestId} completed in ${duration}ms with status ${status}`
// Fluent chains still work
log.withMetadata({ env: 'production' })
.withContext({ service: 'api' })
.info`Processing ${taskCount} items`
Behavior: Interpolations are converted using String(). For structured data, use withMetadata():
// ✅ Good: simple values in templates
log.info`User ${userId} connected from ${ip}`
// ✅ Good: structured data via withMetadata
log.withMetadata({ user: { id: userId, name: name } }).info('User logged in')
// ⚠️ Objects in templates become "[object Object]" (intentional)
log.info`Data: ${myObject}` // Output: "Data: [object Object]"
Metadata (per-message data)
// Attach structured data to a single log entry
log.withMetadata({ userId: '123', action: 'login' }).info('User logged in')
// Log metadata without a message
log.metadataOnly({ status: 'healthy', cpu: '45%' })
Context (persistent data across all log entries)
// Set context - persists across all subsequent logs
log.withContext({ requestId: 'abc-123', service: 'auth' })
log.info('Processing') // includes requestId and service
log.info('Done') // still includes requestId and service
// Read, clear, mute
log.getContext()
log.clearContext() // clear all
log.clearContext(['requestId']) // clear specific keys
log.muteContext() // temporarily disable
log.unMuteContext()
Error Handling
// Error with a message
log.withError(new Error('Connection failed')).error('DB error')
// Error only (no extra message)
log.errorOnly(new Error('Connection failed'))
// Combine error with metadata
log.withError(new Error('Timeout'))
.withMetadata({ query: 'SELECT *', duration: 1500 })
.error('Query failed')
Error Serialization (recommended)
import { serializeError } from 'serialize-error' // npm install serialize-error
const log: ILogLayer = new LogLayer({
errorSerializer: serializeError,
transport: new ConsoleTransport({ logger: console }),
})
Lazy Evaluation
import { LogLayer, lazy } from 'loglayer'
// Context: re-evaluated on every log call
log.withContext({
memoryUsage: lazy(() => process.memoryUsage().heapUsed),
})
// Metadata: evaluated once for that log entry
log.withMetadata({
data: lazy(() => JSON.stringify(largeObject)),
}).debug('Processing result')
// metadataOnly: same behavior
log.metadataOnly({
status: lazy(() => getHealthStatus()),
})
// Async lazy (metadata only) — must be awaited
await log.withMetadata({
result: lazy(async () => await fetchResult()),
}).info('Done')
await log.metadataOnly({
result: lazy(async () => await fetchResult()),
})
// Callbacks are NOT invoked when the log level is disabled
Raw Logging
Full control over all log entry parameters:
import { LogLevel } from 'loglayer'
log.raw({
logLevel: LogLevel.info,
messages: ['User action completed', { userId: 123 }],
metadata: { operation: 'insert', table: 'users' },
rootData: { userId: 123 }, // spread directly at root, bypasses metadataFieldName nesting
error: new Error('Connection timeout'),
context: { requestId: 'req-789' } // overrides context manager for this entry
})
The rootData parameter spreads data flat at the root level of the log entry, ignoring metadataFieldName / contextFieldName nesting.
Configuration
const log: ILogLayer = new LogLayer({
// Required
transport: new ConsoleTransport({ logger: console }),
// Optional
prefix: '[MyApp]', // prepended to all messages
enabled: true, // enable/disable logging
contextFieldName: 'context', // nest context under a field (default: flattened)
metadataFieldName: 'metadata', // nest metadata under a field (default: flattened)
errorFieldName: 'err', // field name for errors (default: 'err')
errorSerializer: (err) => ({ message: err.message, stack: err.stack }),
copyMsgOnOnlyError: false, // copy error.message as log message in errorOnly()
errorFieldInMetadata: false, // nest error inside metadata field
muteContext: false,
muteMetadata: false,
plugins: [],
})
Raw Logging
Bypass the normal API and directly specify all aspects of a log entry:
import { LogLevel } from 'loglayer'
log.raw({
logLevel: LogLevel.info,
messages: ['User action completed', { userId: 123 }],
metadata: { operation: 'insert', table: 'users' },
rootData: { userId: 123 }, // spread directly at root, bypasses metadataFieldName nesting
error: new Error('Connection timeout'),
context: { requestId: 'req-789' } // overrides context manager for this entry
})
The rootData parameter spreads data flat at the root level of the emitted log object, regardless of metadataFieldName / contextFieldName configuration. Use it when you need guaranteed flat structures.
Groups (route logs to specific transports)
const log: ILogLayer = new LogLayer({
transport: [
new ConsoleTransport({ id: 'console', logger: console }),
new DatadogTransport({ id: 'datadog', logger: datadog }),
],
groups: {
database: { transports: ['datadog'], level: 'error' },
auth: { transports: ['datadog', 'console'], level: 'warn' },
},
ungroupedBehavior: 'all', // 'all' (default) | 'none' | string[]
})
// Per-log tagging
log.withGroup('database').error('Connection lost')
log.withGroup(['database', 'auth']).error('Auth DB failure')
// Persistent tagging (child logger)
const dbLogger = log.withGroup('database')
dbLogger.error('Pool exhausted')
// Runtime management
log.disableGroup('database')
log.setGroupLevel('database', 'debug')
log.setActiveGroups(['database']) // only this group active
log.setActiveGroups(null) // all groups active
// Env variable: LOGLAYER_GROUPS=database:debug,auth:warn
Child Loggers
const parentLog: ILogLayer = new LogLayer({
transport: new ConsoleTransport({ logger: console }),
})
parentLog.withContext({ service: 'api' })
// Child inherits config, context, plugins, and groups
const childLog = parentLog.child()
childLog.withContext({ handler: 'users' })
childLog.info('Request received') // has both service and handler context
// Group config is shared by reference — runtime changes propagate both ways
// Persistent group tags (via withGroup()) are copied independently
Message Prefixing
const log: ILogLayer = new LogLayer({
prefix: '[MyApp]',
transport: new ConsoleTransport({ logger: console }),
})
log.info('Started') // "[MyApp] Started"
// Or dynamically
const prefixed = log.withPrefix('[Auth]')
prefixed.info('Login') // "[Auth] Login"
Log Level Control
Log levels are checked at three independent tiers. A log must pass all applicable tiers:
- LogLayer (global) —
setLevel(),enableLogging()— checked first, before any processing - Group —
groups: { database: { level: 'error' } }— only applies to grouped logs - Transport —
new ConsoleTransport({ level: 'warn' })— checked at dispatch time
Log level managers control tier 1 only.
import { LogLevel } from 'loglayer'
log.setLevel(LogLevel.warn) // only warn+ will log
log.enableLogging() // turn on
log.disableLogging() // turn off
log.isLevelEnabled(LogLevel.debug) // check if level is active
Multiple Transports
const log: ILogLayer = new LogLayer({
transport: [
new ConsoleTransport({ logger: console }),
new PinoTransport({ logger: pino() }),
],
})
Testing / Mocking
import { MockLogLayer } from 'loglayer'
// No-op logger for unit tests - same API, does nothing
const log = new MockLogLayer()
Available Transports
Built-in: ConsoleTransport, StructuredTransport, BlankTransport
Logging libraries: Pino, Winston, Bunyan, Consola, Log4js, Roarr, Signale, loglevel, LogTape, tslog, Tracer, Electron Log
Cloud providers: DataDog, AWS CloudWatch, AWS Lambda Powertools, Google Cloud Logging, Azure Monitor, New Relic, Dynatrace, PostHog, Sentry, Axiom, Better Stack, Cribl, Logflare, Sumo Logic, VictoriaLogs
Pretty Printers: Pretty Terminal, Simple Pretty Terminal
Other: HTTP, Log File Rotation, OpenTelemetry
Available Plugins
- Filter: conditionally drop logs
- Redaction: redact sensitive fields from all log data (metadata, context, error, rootData)
- Sprintf: printf-style string formatting
- OpenTelemetry: add trace/span context
- Sampling: randomly drop log entries
- DataDog APM Trace Injector: inject DD trace IDs
Documentation
- Getting Started: Installation and basic usage
- Configuration: All configuration options
- Basic Logging: Log level methods and message formatting
- Context: Persistent context data
- Metadata: Per-message structured data
- Error Handling: Error logging and serialization
- Lazy Evaluation: Deferred computation for context and metadata
- Child Loggers: Creating inherited logger instances
- Log Level Control: Managing log levels
- Transport Overview: All supported transports
- Transport Configuration: Transport setup options
- Plugins Overview: Plugin system and available plugins
- Testing / Mocking: MockLogLayer for tests
- TypeScript Tips: TypeScript-specific guidance
Optional
- Creating Transports: Build custom transports
- Creating Plugins: Build custom plugins
- Context Managers: Customize context inheritance behavior
- Log Level Managers: Customize log level behavior
- Mixins: Extend LogLayer with custom methods
- Wide Events: Accumulate data across async boundaries and emit as single log entry; wide event data is always flat at root level (ignores metadataFieldName); supports configurable sampling (default/per-level) with error/fatal defaulting to 100% (can be overridden)
- Hot-Shots: Emit StatsD/dogstatsd metrics from log entries
- Datadog HTTP Metrics: Send metrics to Datadog HTTP endpoint
- ElysiaJS Integration: ElysiaJS plugin with request-scoped logging, optional
groupconfig to tag auto-logged messages with groups for routing/filtering - Express Integration: Express middleware with request-scoped logging, optional
groupconfig to tag auto-logged messages with groups for routing/filtering - Fastify Integration: Fastify integration with request-scoped logging, optional
groupconfig to tag auto-logged messages with groups for routing/filtering - Hono Integration: Hono integration with request-scoped logging, optional
groupconfig to tag auto-logged messages with groups for routing/filtering - Koa Integration: Koa middleware with request-scoped logging, optional
groupconfig to tag auto-logged messages with groups for routing/filtering - Next.js Integration: Next.js example
- Async Context Tracking: AsyncLocalStorage example
- Wide Events: Comprehensive, self-contained log entries with async context tracking and sampling
- Hot-Shots: StatsD metrics
- Datadog Metrics: Datadog HTTP metrics
- Creating Mixins: Build custom mixins
- Testing Mixins: Test custom mixins
Meet the modern standard for public facing documentation. Beautiful out of the box, easy to maintain, and optimized for user engagement.
Search through billions of items for similar matches to any object, in milliseconds. It’s the next generation of search, an API call away.
Build and deploy reliable background jobs with no timeouts and no infrastructure to manage.
Get the simple developer experience of SQLite in production, and scale your multi-tenant backend with unlimited databases.
Upstash is a serverless data platform providing low latency and high scalability for real-time applications.
One-click deployments built for teams, tuned for Laravel, loaded with tools and goodies you're going to love.