LogLayer

loglayer.dev
Developer Tools

A structured logging library with a fluent API for specifying log messages, metadata and errors

llms.txt

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:

  1. LogLayer (global)setLevel(), enableLogging() — checked first, before any processing
  2. Groupgroups: { database: { level: 'error' } } — only applies to grouped logs
  3. Transportnew 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

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 group config to tag auto-logged messages with groups for routing/filtering
  • Express Integration: Express middleware with request-scoped logging, optional group config to tag auto-logged messages with groups for routing/filtering
  • Fastify Integration: Fastify integration with request-scoped logging, optional group config to tag auto-logged messages with groups for routing/filtering
  • Hono Integration: Hono integration with request-scoped logging, optional group config to tag auto-logged messages with groups for routing/filtering
  • Koa Integration: Koa middleware with request-scoped logging, optional group config to tag auto-logged messages with groups for routing/filtering
  • Next.js Integration: Next.js example
  • Async Context Tracking: AsyncLocalStorage example
Related

The AI Toolkit for TypeScript, from the creators of Next.js.

/llms.txt
136,985 tokens
Developer Tools

Meet the modern standard for public facing documentation. Beautiful out of the box, easy to maintain, and optimized for user engagement.

/llms.txt
5,436 tokens
/llms-full.txt
181,290 tokens
Developer Tools

Web development for the rest of us.

/llms.txt
602 tokens
/llms-full.txt
453,623 tokens
Developer Tools

Search through billions of items for similar matches to any object, in milliseconds. It’s the next generation of search, an API call away.

/llms.txt
15,715 tokens
/llms-full.txt
588,629 tokens
Developer Tools

Build and deploy reliable background jobs with no timeouts and no infrastructure to manage.

/llms.txt
12,202 tokens
/llms-full.txt
387,586 tokens
Developer Tools

Get the simple developer experience of SQLite in production, and scale your multi-tenant backend with unlimited databases.

/llms.txt
10,006 tokens
/llms-full.txt
163,317 tokens
Developer Tools

Upstash is a serverless data platform providing low latency and high scalability for real-time applications.

/llms.txt
52,307 tokens
/llms-full.txt
1,200,134 tokens
Developer Tools

One-click deployments built for teams, tuned for Laravel, loaded with tools and goodies you're going to love.

/llms.txt
565 tokens
/llms-full.txt
11,330 tokens
Developer Tools