Machine Readiness
Stored receipt and evidence
30
100
0
0
0
Samples
No stored offer samples.
Samples
No stored action samples.
Samples
No stored product samples.
Document
User-agent: * Allow: / Sitemap: https://loglayer.dev/sitemap.xml
Document
# 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
```typescript
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
```typescript
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')
```
## Metadata (per-message data)
```typescript
// 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)
```typescript
// 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
```typescript
// 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)
```typescript
import { serializeError } from 'serialize-error' // npm install serialize-error
const log: ILogLayer = new LogLayer({
errorSerializer: serializeError,
transport: new ConsoleTransport({ logger: console }),
})
```
## Lazy Evaluation
```typescript
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
```
## Configuration
```typescript
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: [],
})
```
## Groups (route logs to specific transports)
```typescript
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
```typescript
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
```typescript
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. **Group** — `groups: { database: { level: 'error' } }` — only applies to grouped logs
3. **Transport** — `new ConsoleTransport({ level: 'warn' })` — checked at dispatch time
Log level managers control tier 1 only.
```typescript
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
```typescript
const log: ILogLayer = new LogLayer({
transport: [
new ConsoleTransport({ logger: console }),
new PinoTransport({ logger: pino() }),
],
})
```
## Testing / Mocking
```typescript
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, 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
- Sprintf: printf-style string formatting
- OpenTelemetry: add trace/span context
- DataDog APM Trace Injector: inject DD trace IDs
## Documentation
- [Getting Started](https://loglayer.dev/getting-started): Installation and basic usage
- [Configuration](https://loglayer.dev/configuration): All configuration options
- [Basic Logging](https://loglayer.dev/logging-api/basic-logging): Log level methods and message formatting
- [Context](https://loglayer.dev/logging-api/context): Persistent context data
- [Metadata](https://loglayer.dev/logging-api/metadata): Per-message structured data
- [Error Handling](https://loglayer.dev/logging-api/error-handling): Error logging and serialization
- [Lazy Evaluation](https://loglayer.dev/logging-api/lazy-evaluation): Deferred computation for context and metadata
- [Child Loggers](https://loglayer.dev/logging-api/child-loggers): Creating inherited logger instances
- [Log Level Control](https://loglayer.dev/logging-api/adjusting-log-levels): Managing log levels
- [Transport Overview](https://loglayer.dev/transports/): All supported transports
- [Transport Configuration](https://loglayer.dev/transports/configuration): Transport setup options
- [Plugins Overview](https://loglayer.dev/plugins): Plugin system and available plugins
- [Testing / Mocking](https://loglayer.dev/logging-api/unit-testing): MockLogLayer for tests
- [TypeScript Tips](https://loglayer.dev/logging-api/typescript): TypeScript-specific guidance
## Optional
- [Creating Transports](https://loglayer.dev/transports/creating-transports): Build custom transports
- [Creating Plugins](https://loglayer.dev/plugins/creating-plugins): Build custom plugins
- [Context Managers](https://loglayer.dev/context-managers/): Customize context inheritance behavior
- [Log Level Managers](https://loglayer.dev/log-level-managers/): Customize log level behavior
- [Mixins](https://loglayer.dev/mixins/): Extend LogLayer with custom methods
- [ElysiaJS Integration](https://loglayer.dev/integrations/elysia): ElysiaJS plugin with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Express Integration](https://loglayer.dev/integrations/express): Express middleware with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Fastify Integration](https://loglayer.dev/integrations/fastify): Fastify integration with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Hono Integration](https://loglayer.dev/integrations/hono): Hono integration with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Koa Integration](https://loglayer.dev/integrations/koa): Koa middleware with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Next.js Integration](https://loglayer.dev/example-integrations/nextjs): Next.js example
- [Async Context Tracking](https://loglayer.dev/example-integrations/async-context): AsyncLocalStorage example
Document
# 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.
LogLayer is a unified logging layer that sits on top of logging libraries (pino, winston, bunyan, etc.) and cloud providers (DataDog, AWS CloudWatch, Google Cloud Logging, etc.). It provides a consistent API regardless of the underlying transport, with features for structured logging, context management, error handling, plugins, and more.
## Installation
```
npm install loglayer
```
Also works with pnpm, yarn, bun, and deno.
## Quick Start
```typescript
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
LogLayer provides six log levels, ordered by severity:
| Level | Value | Description |
|-------|-------|-------------|
| trace | 10 | Most verbose, detailed debugging |
| debug | 20 | Debug information |
| info | 30 | Informational messages |
| warn | 40 | Warning messages |
| error | 50 | Error messages |
| fatal | 60 | Critical failures |
```typescript
log.trace('Detailed debugging')
log.debug('Debug information')
log.info('Informational message')
log.warn('Warning message')
log.error('Error occurred')
log.fatal('Critical failure')
```
All log methods accept multiple parameters (strings, booleans, numbers, null, undefined):
```typescript
log.info('User', 123, 'logged in')
log.info('User %s logged in from %s', 'john', 'localhost')
```
## Metadata (per-message structured data)
Metadata is attached to a single log entry and not persisted.
```typescript
// Attach structured data to a single log entry
log.withMetadata({
userId: '123',
action: 'login',
browser: 'Chrome'
}).info('User logged in')
```
By default, metadata is flattened into the log object root:
```json
{ "msg": "User logged in", "userId": "123", "action": "login", "browser": "Chrome" }
```
### Metadata-Only Logging
Log metadata without a message:
```typescript
log.metadataOnly({ status: 'healthy', cpu: '45%' })
// With specific log level
log.metadataOnly({ status: 'warning', cpu: '90%' }, LogLevel.warn)
```
### Nested Metadata Field
Place metadata in a dedicated field:
```typescript
const log: ILogLayer = new LogLayer({
metadataFieldName: 'metadata',
transport: new ConsoleTransport({ logger: console }),
})
log.withMetadata({ userId: '123' }).info('User logged in')
// Output: { "msg": "User logged in", "metadata": { "userId": "123" } }
```
### Muting Metadata
```typescript
const log: ILogLayer = new LogLayer({ muteMetadata: true, transport: ... })
// Or at runtime:
log.muteMetadata()
log.unMuteMetadata()
```
## Context (persistent data across all log entries)
Context data persists across all subsequent log entries until explicitly cleared.
```typescript
log.withContext({
requestId: '123',
userId: 'user_456'
})
log.info('Processing request') // includes requestId and userId
log.warn('Rate limit') // includes requestId and userId
```
By default, context is flattened into the log object root:
```json
{ "msg": "Processing request", "requestId": "123", "userId": "user_456" }
```
Note: Passing empty values (null, undefined, {}) to withContext does nothing. Use clearContext() instead.
### Nested Context Field
```typescript
const log: ILogLayer = new LogLayer({
contextFieldName: 'context',
transport: new ConsoleTransport({ logger: console }),
})
log.withContext({ requestId: '123' }).info('Processing')
// Output: { "msg": "Processing", "context": { "requestId": "123" } }
```
### Combining Context and Metadata Fields
If you set the same field name for both, they merge:
```typescript
const log: ILogLayer = new LogLayer({
contextFieldName: 'data',
metadataFieldName: 'data',
transport: new ConsoleTransport({ logger: console }),
})
log.withContext({ requestId: '123' })
.withMetadata({ duration: 1500 })
.info('Request completed')
// Output: { "msg": "Request completed", "data": { "requestId": "123", "duration": 1500 } }
```
### Managing Context
```typescript
// Get current context
const context = log.getContext()
// Clear all context
log.clearContext()
// Clear specific keys
log.clearContext('userId')
log.clearContext(['requestId', 'sessionId'])
// Mute/unmute context
log.muteContext()
log.unMuteContext()
```
### Context with Errors and Metadata
```typescript
log.withContext({ requestId: '123' })
.withError(new Error('Not found'))
.error('Failed to fetch user')
log.withContext({ requestId: '123' })
.withMetadata({ userId: 'user_456' })
.info('User logged in')
```
## Error Handling
### Error with a Message
```typescript
const error = new Error('Database connection failed')
log.withError(error).error('Failed to process request')
// Any log level works
log.withError(error).warn('Database connection unstable')
log.withError(error).info('Retrying connection')
```
### Error-Only Logging
```typescript
log.errorOnly(new Error('Database connection failed'))
// With custom log level
log.errorOnly(new Error('Connection timeout'), { logLevel: LogLevel.warn })
```
### Error Configuration
```typescript
const log: ILogLayer = new LogLayer({
// Field name for errors (default: 'err')
errorFieldName: 'error',
// Custom error serializer
errorSerializer: (err) => ({ message: err.message, stack: err.stack, code: err.code }),
// Copy error.message as the log message when using errorOnly()
copyMsgOnOnlyError: true,
// Place error inside the metadata field
errorFieldInMetadata: true,
transport: new ConsoleTransport({ logger: console }),
})
```
### Error Serialization (recommended)
JavaScript Error objects don't serialize to JSON well. Use `serialize-error`:
```typescript
import { serializeError } from 'serialize-error' // npm install serialize-error
const log: ILogLayer = new LogLayer({
errorSerializer: serializeError,
transport: new ConsoleTransport({ logger: console }),
})
```
### Combining Errors with Other Data
```typescript
log.withError(new Error('Query failed'))
.withMetadata({ query: 'SELECT * FROM users', duration: 1500 })
.error('Database error')
log.withContext({ requestId: '123' })
.withError(new Error('Not found'))
.error('Resource not found')
```
## Lazy Evaluation
The `lazy()` function defers evaluation of a value until log time. The callback is only invoked when the log level is enabled, and is re-evaluated on each log call. It works with `withContext()`, `withMetadata()`, and `metadataOnly()`.
```typescript
import { LogLayer, lazy } from 'loglayer'
let currentUser = null
// Context: evaluated fresh on each log call
log.withContext({
memoryUsage: lazy(() => process.memoryUsage().heapUsed),
user: lazy(() => currentUser?.id ?? null),
})
log.info('Server status check')
// Output: { memoryUsage: 52428800, user: null, msg: "Server status check" }
currentUser = { id: 'user_123' }
log.info('User action')
// Output: { memoryUsage: 52432000, user: "user_123", msg: "User action" }
// Metadata: same behavior
log.withMetadata({
data: lazy(() => JSON.stringify(largeObject)),
}).debug('Processing result')
// metadataOnly: same behavior
log.metadataOnly({
status: lazy(() => getHealthStatus()),
uptime: lazy(() => process.uptime()),
})
// Callbacks are NOT invoked when the log level is disabled
log.setLevel('warn')
log.debug('This will not trigger any lazy callbacks')
```
### Async Lazy (metadata only)
`lazy()` also accepts async callbacks in metadata. When async lazy values are present, the log method returns `Promise<void>` that must be awaited. This works with both `withMetadata()` and `metadataOnly()`:
```typescript
await log.withMetadata({
result: lazy(async () => await fetchResult()),
dbStatus: lazy(async () => await db.ping()),
}).info('Processing complete')
await log.metadataOnly({
result: lazy(async () => await fetchResult()),
})
```
Async lazy callbacks are **not supported in context**. If you need async data in context, resolve it before calling `withContext()`.
### Error Handling
If a lazy callback throws or rejects, LogLayer replaces the value with `"[LazyEvalError]"`, still sends the log entry, and emits a separate error-level log.
```typescript
import { LAZY_EVAL_ERROR } from 'loglayer'
// Check for failed lazy values
if (someValue === LAZY_EVAL_ERROR) {
// Handle the error
}
```
### Notes
- `lazy()` can only be used at the root level of context and metadata objects
- Async lazy callbacks are only supported in `withMetadata()` and `metadataOnly()`, not `withContext()`
- `getContext()` resolves lazy values by default; use `getContext({ raw: true })` for raw wrappers
- Child loggers inherit the lazy wrapper, not the resolved value
## Configuration
### Full Configuration Example
```typescript
import { LogLayer, ConsoleTransport } from 'loglayer'
const log: ILogLayer = new LogLayer({
// Required: transport(s)
transport: new ConsoleTransport({ logger: console }),
// Message prefix
prefix: '[MyApp]',
// Enable/disable logging (default: true)
enabled: true,
// Debug mode - outputs to console before sending to transport
consoleDebug: false,
// Error handling
errorSerializer: (err) => ({ message: err.message, stack: err.stack }),
errorFieldName: 'err', // default: 'err'
copyMsgOnOnlyError: false, // copy error.message as log message in errorOnly()
errorFieldInMetadata: false, // nest error inside metadata field
// Data structure
contextFieldName: 'context', // nest context (default: flattened)
metadataFieldName: 'metadata', // nest metadata (default: flattened)
muteContext: false,
muteMetadata: false,
// Plugins
plugins: [],
// Groups (route logs to specific transports)
groups: {
database: { transports: ['datadog'], level: 'error' },
auth: { transports: ['sentry', 'datadog'], level: 'warn' },
},
activeGroups: null, // null = all groups active; string[] to restrict
ungroupedBehavior: 'all', // 'all' | 'none' | string[]
})
```
### Retrieving Configuration
```typescript
const config = log.getConfig()
```
## Message Prefixing
```typescript
// Via configuration
const log: ILogLayer = new LogLayer({
prefix: '[MyApp]',
transport: new ConsoleTransport({ logger: console }),
})
log.info('Started') // "[MyApp] Started"
// Via method (creates a new logger instance)
const prefixed = log.withPrefix('[Auth]')
prefixed.info('Login') // "[Auth] Login"
```
## Child Loggers
Child loggers inherit configuration, context, plugins, and groups from their parent.
```typescript
const parentLog: ILogLayer = new LogLayer({
transport: new ConsoleTransport({ logger: console }),
})
parentLog.withContext({ service: 'api' })
const childLog = parentLog.child()
childLog.withContext({ handler: 'users' })
childLog.info('Request received') // has both service and handler context
```
Context inheritance behavior depends on the Context Manager being used (default: shallow copy, independent).
### Group Inheritance
Group configuration and active groups are shared by reference between parent and child. Runtime changes (e.g., `addGroup()`, `setGroupLevel()`, `setActiveGroups()`) on either logger affect both. Persistent group tags (assigned via `withGroup()`) are copied independently — a child's tags don't affect the parent.
## Log Level Control
Log levels are checked at three independent tiers. A log must pass all applicable tiers to reach a transport:
| Order | Tier | Configured via | Scope |
|-------|------|----------------|-------|
| 1 | **LogLayer (global)** | `setLevel()`, `enableLogging()` | All logs, checked first |
| 2 | **Group** | `groups: { database: { level: 'error' } }` | Only grouped logs |
| 3 | **Transport** | `new ConsoleTransport({ level: 'warn' })` | Per-transport, checked at dispatch |
Each tier is an independent gate. When no groups are configured, only tiers 1 and 3 apply. Log level managers control tier 1 only.
```typescript
import { LogLevel } from 'loglayer'
// Set minimum level (all levels at or above are enabled)
log.setLevel(LogLevel.warn) // only warn, error, fatal
// Enable/disable all logging
log.enableLogging()
log.disableLogging()
// Enable/disable individual levels (ignores hierarchy)
log.enableIndividualLevel(LogLevel.debug)
log.disableIndividualLevel(LogLevel.debug)
// Check if a level is enabled
log.isLevelEnabled(LogLevel.debug)
```
## Raw Logging
Bypass the normal API and directly specify all aspects of a log entry:
```typescript
import { LogLevel } from 'loglayer'
log.raw({
logLevel: LogLevel.info,
messages: ['User action completed', { userId: 123 }],
metadata: { operation: 'insert', table: 'users' },
error: new Error('Connection timeout'),
context: { requestId: 'req-789' } // overrides context manager for this entry
})
```
## Multiple Transports
```typescript
import { PinoTransport } from '@loglayer/transport-pino'
import { WinstonTransport } from '@loglayer/transport-winston'
const log: ILogLayer = new LogLayer({
transport: [
new PinoTransport({ logger: pinoLogger }),
new WinstonTransport({ logger: winstonLogger }),
],
})
```
## Groups (route logs to specific transports)
Groups are named routing rules that give fine-grained control over which logs go to which transports. In a large system with many subsystems (database, auth, payments, etc.), groups let you "listen" to only certain categories of logs instead of adjusting global log levels.
### Configuration
```typescript
const log: ILogLayer = new LogLayer({
transport: [
new ConsoleTransport({ id: 'console', logger: console }),
new DatadogTransport({ id: 'datadog', logger: datadog }),
new SentryTransport({ id: 'sentry', logger: sentry }),
],
groups: {
database: { transports: ['datadog'], level: 'error' },
auth: { transports: ['sentry', 'datadog'], level: 'warn' },
},
activeGroups: null, // null = all groups active; string[] to restrict
ungroupedBehavior: 'all', // 'all' (default) | 'none' | string[]
})
```
### Group Options
| Name | Type | Default | Description |
|------|------|---------|-------------|
| `transports` | `string[]` | (required) | Transport IDs this group routes to |
| `level` | `LogLevelType` | `"trace"` | Minimum log level for this group |
| `enabled` | `boolean` | `true` | Whether this group is active |
### Per-Log Tagging
Tag individual log entries with one or more groups:
```typescript
log.withGroup('database').error('Connection timeout')
// Combine with metadata and errors
log.withMetadata({ query: 'SELECT *' }).withGroup('database').error('Query failed')
// Multiple groups — log goes to union of both groups' transports
log.withGroup(['database', 'auth']).error('Auth DB connection failed')
```
### Persistent Tagging (Child Loggers)
Use `withGroup()` on a LogLayer instance to create a child logger with groups permanently assigned:
```typescript
const dbLogger = log.withGroup('database')
dbLogger.error('Pool exhausted') // always routed through 'database' group
dbLogger.info('Connected') // also routed through 'database' group
// Groups are additive across child loggers
const authDbLogger = log.withGroup('auth').withGroup('database')
authDbLogger.error('Auth DB failure') // routes to both auth + database transports
```
### Group Level Filtering
Each group has its own minimum log level. Logs below the group's level are dropped for that group:
```typescript
log.withGroup('database').info('Query took 50ms') // dropped (below 'error')
log.withGroup('database').error('Connection lost') // sent to datadog
```
### Ungrouped Logs
The `ungroupedBehavior` config controls what happens to logs with no group tag:
```typescript
{ ungroupedBehavior: 'all' } // default: go to ALL transports (backward compatible)
{ ungroupedBehavior: 'none' } // drop ungrouped logs
{ ungroupedBehavior: ['console'] } // only to listed transports
```
### Active Groups Filter
Restrict which groups are active. Logs tagged with inactive groups are dropped:
```typescript
const log = new LogLayer({
...
activeGroups: ['database'], // only database group is active
})
```
### Environment Variable
The `LOGLAYER_GROUPS` env variable overrides `activeGroups` at construction time:
```bash
LOGLAYER_GROUPS=database,auth
LOGLAYER_GROUPS=database:debug,auth:warn # with level overrides
```
### Runtime Management
```typescript
log.addGroup('inbox', { transports: ['datadog'], level: 'error' })
log.removeGroup('inbox')
log.enableGroup('database')
log.disableGroup('database')
log.setGroupLevel('database', 'debug')
log.setActiveGroups(['database']) // only database active
log.setActiveGroups(null) // all groups active
log.getGroups() // returns all group configs
```
### Plugin Integration
The `shouldSendToLogger` plugin hook receives the `groups` array:
```typescript
{
shouldSendToLogger: ({ groups, transportId, logLevel }) => {
if (groups?.includes('sensitive')) {
return transportId === 'encrypted-transport'
}
return true
}
}
```
### Transport Integration
Transports receive the `groups` array in `LogLayerTransportParams`:
```typescript
shipToLogger({ logLevel, messages, groups }) {
if (groups) {
// Include group info in structured output
}
}
```
## Transport Management
Dynamically add, remove, and replace transports at runtime.
```typescript
// Add a transport (replaces existing with same ID)
log.addTransport(new PinoTransport({ logger: pino(), id: 'pino' }))
// Add multiple
log.addTransport([
new ConsoleTransport({ logger: console, id: 'console' }),
new PinoTransport({ logger: pino(), id: 'pino' }),
])
// Remove a transport by ID
log.removeTransport('console') // returns true if found
// Replace all transports
log.withFreshTransports(new PinoTransport({ logger: pino() }))
// Get underlying logger instance by transport ID
const pinoLogger = log.getLoggerInstance<P.Pino>('pino')
```
## Transport Configuration
All transports share common configuration options:
```typescript
new PinoTransport({
id: 'main-pino', // unique identifier
logger: pinoLogger, // the logger instance
enabled: true, // enable/disable (default: true)
consoleDebug: false, // also log to console for debugging
level: 'info', // minimum log level (default: 'trace')
})
```
## Console Transport
Built-in transport using the standard console object:
```typescript
import { LogLayer, ConsoleTransport } from 'loglayer'
const log: ILogLayer = new LogLayer({
transport: new ConsoleTransport({
logger: console,
appendObjectData: false, // false: data first, true: data last
messageField: 'msg', // place message in a field for structured logging
dateField: 'timestamp', // auto-add ISO date
levelField: 'level', // auto-add log level
dateFn: () => Date.now(), // custom date function
levelFn: (level) => level.toUpperCase(), // custom level function
stringify: false, // JSON.stringify structured output
messageFn: ({ logLevel, messages }) => `[${logLevel}] ${messages.join(' ')}`, // custom format
})
})
```
### Structured Logging with Console
```typescript
const log: ILogLayer = new LogLayer({
transport: new ConsoleTransport({
logger: console,
messageField: 'msg',
dateField: 'timestamp',
levelField: 'level',
})
})
log.withMetadata({ user: 'john' }).info('User logged in')
// console.info({ user: 'john', msg: 'User logged in', timestamp: '2023-12-01...', level: 'info' })
```
## Structured Logger Transport
Built-in console transport with structured logging enabled by default (level, time, msg fields):
```typescript
import { LogLayer, StructuredTransport } from 'loglayer'
const log: ILogLayer = new LogLayer({
transport: new StructuredTransport({
logger: console,
})
})
log.withMetadata({ user: 'john' }).info('User logged in')
// console.info({ level: 'info', time: '2025-01-01T...', msg: 'User logged in', user: 'john' })
```
Options: `messageField` (default: 'msg'), `levelField` (default: 'level'), `dateField` (default: 'time'), `dateFn`, `levelFn`, `stringify`, `messageFn`, `level`.
## Pino Transport
```typescript
import { pino } from 'pino'
import { LogLayer } from 'loglayer'
import { PinoTransport } from '@loglayer/transport-pino'
const log: ILogLayer = new LogLayer({
transport: new PinoTransport({
logger: pino({ level: 'trace' }),
})
})
```
Install: `npm install loglayer @loglayer/transport-pino pino`
## Winston Transport
```typescript
import winston from 'winston'
import { LogLayer } from 'loglayer'
import { WinstonTransport } from '@loglayer/transport-winston'
const log: ILogLayer = new LogLayer({
transport: new WinstonTransport({
logger: winston.createLogger({}),
})
})
```
Install: `npm install loglayer @loglayer/transport-winston winston`
## Blank Transport (custom transport)
Quickly create custom transports without extending classes:
```typescript
import { LogLayer, BlankTransport } from 'loglayer'
const log: ILogLayer = new LogLayer({
transport: new BlankTransport({
shipToLogger: ({ logLevel, messages, data, hasData }) => {
console.log(`[${logLevel}]`, ...messages, data && hasData ? data : '')
return messages
}
})
})
```
## HTTP Transport
Ships logs to any HTTP endpoint with batching, compression, retries, and rate limiting:
```typescript
import { LogLayer } from 'loglayer'
import { HttpTransport } from '@loglayer/transport-http'
import { serializeError } from 'serialize-error'
const log: ILogLayer = new LogLayer({
errorSerializer: serializeError,
transport: new HttpTransport({
url: 'https://api.example.com/logs',
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
payloadTemplate: ({ logLevel, message, data }) =>
JSON.stringify({ timestamp: new Date().toISOString(), level: logLevel, message, metadata: data }),
compression: true,
maxRetries: 3,
retryDelay: 1000,
respectRateLimit: true,
enableBatchSend: true,
batchSize: 100,
batchSendTimeout: 5000,
batchSendDelimiter: '\n',
batchMode: 'delimiter', // 'delimiter' | 'array' | 'field'
onError: (err) => console.error('Failed:', err),
})
})
```
Install: `npm install loglayer @loglayer/transport-http serialize-error`
## Creating Custom Transports
### Logger-Based (wraps a logging library)
```typescript
import { BaseTransport, LogLevel, type LogLayerTransportParams } from '@loglayer/transport'
export class CustomTransport extends BaseTransport<YourLoggerType> {
shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams) {
if (data && hasData) messages.unshift(data)
switch (logLevel) {
case LogLevel.info: this.logger.info(...messages); break
case LogLevel.warn: this.logger.warn(...messages); break
case LogLevel.error: this.logger.error(...messages); break
// ... other levels
}
return messages
}
}
```
### Loggerless (no external logger instance)
```typescript
import { LoggerlessTransport, type LogLayerTransportParams } from '@loglayer/transport'
export class CustomServiceTransport extends LoggerlessTransport {
shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams) {
const payload = {
level: logLevel,
message: messages.join(' '),
...(data && hasData ? data : {})
}
this.service.send(payload)
return messages
}
}
```
### shipToLogger Parameters
```typescript
interface LogLayerTransportParams {
logLevel: LogLevel
messages: any[]
data?: Record<string, any> // combined metadata + context + error
hasData?: boolean
metadata?: Record<string, any> // individual metadata
error?: any // individual error
context?: Record<string, any> // individual context
groups?: string[] // group tags for this log entry
}
```
### Resource Cleanup
Transports can implement the Disposable interface for cleanup when removed:
```typescript
export class MyTransport extends LoggerlessTransport implements Disposable {
[Symbol.dispose](): void {
this.client?.close()
}
}
```
## Plugins
Plugins modify logging behavior at various points in the log lifecycle.
### Adding Plugins
```typescript
const log: ILogLayer = new LogLayer({
transport: new ConsoleTransport({ logger: console }),
plugins: [
{
id: 'my-plugin',
onBeforeDataOut({ data }) { return { ...data, timestamp: Date.now() } }
}
]
})
// Or add later
log.addPlugins([myPlugin])
```
### Managing Plugins
```typescript
log.enablePlugin('my-plugin')
log.disablePlugin('my-plugin')
log.removePlugin('my-plugin')
log.withFreshPlugins([newPlugin1, newPlugin2])
```
### Plugin Lifecycle Methods
```typescript
interface LogLayerPlugin {
id?: string
disabled?: boolean
// Modify data (metadata + context + error) before transport
onBeforeDataOut?(params: { logLevel, data, metadata, error, context }, loglayer): Record<string, any> | null
// Modify messages before transport
onBeforeMessageOut?(params: { messages, logLevel }, loglayer): MessageDataType[]
// Transform the log level dynamically
transformLogLevel?(params: { logLevel, messages, data, metadata, error, context }, loglayer): LogLevelType | null
// Control whether a log should be sent (return false to drop)
shouldSendToLogger?(params: { messages, logLevel, transportId, data, metadata, error, context, groups }, loglayer): boolean
// Intercept withMetadata() / metadataOnly() calls
onMetadataCalled?(metadata, loglayer): Record<string, any> | null
// Intercept withContext() calls
onContextCalled?(context, loglayer): Record<string, any> | null
}
```
### Example: Custom Plugin
```typescript
import type { LogLayerPlugin } from 'loglayer'
const timestampPlugin: LogLayerPlugin = {
onBeforeMessageOut: ({ messages }) => {
return messages.map(msg => `[${new Date().toISOString()}] ${msg}`)
}
}
const filterPlugin: LogLayerPlugin = {
shouldSendToLogger: ({ logLevel }) => {
return process.env.NODE_ENV !== 'production' || logLevel !== 'debug'
}
}
const enrichPlugin: LogLayerPlugin = {
onBeforeDataOut: ({ data }) => ({
...(data || {}),
environment: process.env.NODE_ENV,
timestamp: new Date().toISOString(),
})
}
```
## Filter Plugin
Filter logs using string patterns, regular expressions, or JSON Queries:
```typescript
import { filterPlugin } from '@loglayer/plugin-filter'
// String pattern matching
const filter = filterPlugin({ messages: ['error', 'warning'] })
// Regex matching
const regexFilter = filterPlugin({ messages: [/error/i, /warning\d+/] })
// JSON Query filtering
const queryFilter = filterPlugin({
queries: [
'.level == "error"',
'.data.userId == 123',
'(.level == "error") and (.data.retryCount > 3)',
],
})
```
Install: `npm install @loglayer/plugin-filter`
## Redaction Plugin
Redact sensitive fields from metadata using fast-redact:
```typescript
import { redactionPlugin } from '@loglayer/plugin-redaction'
const log: ILogLayer = new LogLayer({
transport: new ConsoleTransport({ logger: console }),
plugins: [
redactionPlugin({
paths: ['password', 'creditCard', 'user.ssn', 'payment.*.number'],
censor: '[REDACTED]', // default, or a function: (v) => '***'
remove: false, // true to remove keys entirely
}),
],
})
```
Install: `npm install @loglayer/plugin-redaction`
## Sprintf Plugin
Printf-style string formatting:
```typescript
import { sprintfPlugin } from '@loglayer/plugin-sprintf'
const log: ILogLayer = new LogLayer({
transport: new ConsoleTransport({ logger: console }),
plugins: [sprintfPlugin()],
})
log.info('Hello %s!', 'world') // "Hello world!"
log.info('Number: %d', 42) // "Number: 42"
```
Install: `npm install @loglayer/plugin-sprintf`
## Testing / Mocking
### MockLogLayer
A no-op implementation of ILogLayer for unit tests:
```typescript
import { MockLogLayer, ILogLayer } from 'loglayer'
class UserService {
constructor(private logger: ILogLayer) {}
createUser(name: string) {
this.logger.withMetadata({ name }).info('Creating user')
}
}
// In tests:
const mockLog = new MockLogLayer()
const service = new UserService(mockLog)
service.createUser('john') // no output, no errors
```
### Spying on Mock Methods
```typescript
import { vi } from 'vitest'
import { MockLogLayer } from 'loglayer'
const logger = new MockLogLayer()
// Spy on direct method
const infoSpy = vi.spyOn(logger, 'info')
logger.info('testing')
expect(infoSpy).toBeCalledWith('testing')
// Spy on chained methods
const builder = logger.getMockLogBuilder()
const metadataSpy = vi.spyOn(builder, 'withMetadata')
const errorSpy = vi.spyOn(builder, 'withError')
const builderInfoSpy = vi.spyOn(builder, 'info')
logger.withMetadata({ test: 'test' }).withError(new Error('err')).info('msg')
expect(metadataSpy).toBeCalledWith({ test: 'test' })
expect(errorSpy).toBeCalledWith(expect.any(Error))
expect(builderInfoSpy).toBeCalledWith('msg')
```
## TypeScript Tips
### Use ILogLayer for typing
```typescript
import type { ILogLayer } from 'loglayer'
const logger: ILogLayer = new LogLayer({ ... })
```
### Type log level values
```typescript
import type { LogLevelType } from 'loglayer'
const level: LogLevelType = process.env.LOG_LEVEL as LogLevelType
```
### Type transport arrays
```typescript
import type { LogLayerTransport } from 'loglayer'
const transports: LogLayerTransport[] = [...]
```
### Custom IntelliSense via declaration merging
Create a `loglayer.d.ts` in your project:
```typescript
declare module 'loglayer' {
interface LogLayerContext {
userId?: string
sessionId?: string
requestId?: string
[key: string]: any
}
interface LogLayerMetadata {
operation?: string
duration?: number
[key: string]: any
}
}
```
## Context Managers
Context managers control how context data behaves between parent and child loggers. Default is used automatically.
### Available Context Managers
- **Default** (built-in): Child gets a shallow copy of parent context at creation. Independent after that.
- **Isolated** (`@loglayer/context-manager-isolated`): Child starts with no context.
- **Linked** (`@loglayer/context-manager-linked`): Context is shared - changes in any logger affect all linked loggers.
### Using a Custom Context Manager
```typescript
import { LinkedContextManager } from '@loglayer/context-manager-linked'
const log: ILogLayer = new LogLayer({
transport: new ConsoleTransport({ logger: console }),
}).withContextManager(new LinkedContextManager())
```
### Creating Custom Context Managers
Implement the IContextManager interface from `@loglayer/context-manager`:
```typescript
interface IContextManager {
setContext(context?: Record<string, any>): void
appendContext(context: Record<string, any>): void
getContext(): Record<string, any>
hasContextData(): boolean
clearContext(keys?: string | string[]): void
onChildLoggerCreated(params: OnChildLoggerCreatedParams): void
clone(): IContextManager
}
```
## Log Level Managers
Log level managers control how log levels are inherited between parent and child loggers. Default is used automatically.
### Available Log Level Managers
- **Default** (built-in): Child inherits parent level at creation, independent after that.
- **Global** (`@loglayer/log-level-manager-global`): All loggers share the same global log level.
- **One Way** (`@loglayer/log-level-manager-one-way`): Parent changes propagate to children, but not vice versa.
- **Linked** (`@loglayer/log-level-manager-linked`): Bidirectional - changes in any logger affect all linked loggers in the hierarchy.
### Using a Custom Log Level Manager
```typescript
import { GlobalLogLevelManager } from '@loglayer/log-level-manager-global'
const log: ILogLayer = new LogLayer({
transport: new ConsoleTransport({ logger: console }),
}).withLogLevelManager(new GlobalLogLevelManager())
```
### Creating Custom Log Level Managers
Implement the ILogLevelManager interface from `@loglayer/log-level-manager`:
```typescript
interface ILogLevelManager {
setLevel(logLevel: LogLevelType): void
enableIndividualLevel(logLevel: LogLevelType): void
disableIndividualLevel(logLevel: LogLevelType): void
isLevelEnabled(logLevel: LogLevelType): boolean
enableLogging(): void
disableLogging(): void
onChildLoggerCreated(params: OnChildLogLevelManagerCreatedParams): void
clone(): ILogLevelManager
}
```
## Mixins
Mixins extend LogLayer with custom methods without inheritance. They augment the prototype directly.
### Using Mixins
```typescript
import { LogLayer, useLogLayerMixin, ConsoleTransport } from 'loglayer'
import { hotshotsMixin } from '@loglayer/mixin-hot-shots'
import { StatsD } from 'hot-shots'
const statsd = new StatsD({ host: 'localhost', port: 8125 })
// Register BEFORE creating LogLayer instances
useLogLayerMixin(hotshotsMixin(statsd))
const log: ILogLayer = new LogLayer({
transport: new ConsoleTransport({ logger: console }),
})
log.stats.increment('request.count').send()
log.stats.timing('request.duration', 150).send()
```
### TypeScript Setup for Mixins
Add the mixin package to your tsconfig.json:
```json
{
"include": ["./node_modules/@loglayer/mixin-hot-shots"]
}
```
### Creating Custom Mixins
```typescript
import { LogLayer, useLogLayerMixin, LogLayerMixinAugmentType } from 'loglayer'
import type { LogLayerMixin, LogLayerMixinRegistration, MockLogLayer } from 'loglayer'
// 1. Define types
export interface IMetricsMixin<T> {
recordMetric(name: string, value: number): T
}
declare module 'loglayer' {
interface LogLayer extends IMetricsMixin<LogLayer> {}
interface MockLogLayer extends IMetricsMixin<MockLogLayer> {}
interface ILogLayer<This> extends IMetricsMixin<This> {}
}
// 2. Implement
const metricsMixin: LogLayerMixin = {
augmentationType: LogLayerMixinAugmentType.LogLayer,
augment: (prototype) => {
prototype.recordMetric = function(this: LogLayer, name: string, value: number) {
console.log(`Metric: ${name} = ${value}`)
return this
}
},
augmentMock: (prototype) => {
prototype.recordMetric = function(this: MockLogLayer, name: string, value: number) {
return this // no-op for tests
}
}
}
// 3. Register
useLogLayerMixin({ mixinsToAdd: [metricsMixin] })
```
## Available Transports
### Built-in (included with loglayer)
- ConsoleTransport - Browser/server console
- StructuredTransport - Console-based structured logging with level, time, and msg fields enabled by default
- BlankTransport - Custom transport via shipToLogger function
### Logging Libraries
- `@loglayer/transport-pino` - Pino
- `@loglayer/transport-winston` - Winston
- `@loglayer/transport-bunyan` - Bunyan
- `@loglayer/transport-consola` - Consola
- `@loglayer/transport-log4js-node` - Log4js
- `@loglayer/transport-roarr` - Roarr
- `@loglayer/transport-signale` - Signale
- `@loglayer/transport-loglevel` - loglevel
- `@loglayer/transport-logtape` - LogTape
- `@loglayer/transport-tslog` - tslog
- `@loglayer/transport-tracer` - Tracer
- `@loglayer/transport-electron-log` - Electron Log
### Cloud Providers
- `@loglayer/transport-datadog` - DataDog (server-side)
- `@loglayer/transport-datadog-browser-logs` - DataDog Browser Logs
- `@loglayer/transport-aws-cloudwatch-logs` - Amazon CloudWatch Logs
- `@loglayer/transport-aws-lambda-powertools` - AWS Lambda Powertools
- `@loglayer/transport-google-cloud-logging` - Google Cloud Logging
- `@loglayer/transport-dynatrace` - Dynatrace
- `@loglayer/transport-new-relic` - New Relic
- `@loglayer/transport-sentry` - Sentry
- `@loglayer/transport-axiom` - Axiom
- `@loglayer/transport-betterstack` - Better Stack
- `@loglayer/transport-cribl-http` - Cribl Stream (via HTTP/S Bulk API)
- `@loglayer/transport-logflare` - Logflare
- `@loglayer/transport-sumo-logic` - Sumo Logic
- `@loglayer/transport-victoria-logs` - VictoriaLogs
### Pretty Printers
- `@loglayer/transport-pretty-terminal` - Enhanced terminal output
- `@loglayer/transport-simple-pretty-terminal` - Simple pretty terminal output
### Other
- `@loglayer/transport-http` - HTTP endpoint (batching, compression, retries)
- `@loglayer/transport-log-file-rotation` - Log file rotation with compression
- `@loglayer/transport-opentelemetry` - OpenTelemetry
## Available Plugins
- `@loglayer/plugin-filter` - Filter logs by patterns, regex, or JSON queries
- `@loglayer/plugin-redaction` - Redact sensitive fields
- `@loglayer/plugin-sprintf` - Printf-style string formatting
- `@loglayer/plugin-opentelemetry` - OpenTelemetry trace/span context
- `@loglayer/plugin-datadog-apm-trace-injector` - DataDog APM trace IDs
## Available Mixins
- `@loglayer/mixin-hot-shots` - Hot-Shots (StatsD) metrics
## Available Context Managers
- Default (built-in) - independent copy on child creation
- `@loglayer/context-manager-isolated` - children start with no context
- `@loglayer/context-manager-linked` - shared context across hierarchy
## Available Log Level Managers
- Default (built-in) - child inherits at creation, independent after
- `@loglayer/log-level-manager-global` - global shared state
- `@loglayer/log-level-manager-one-way` - parent propagates to children
- `@loglayer/log-level-manager-linked` - bidirectional propagation
## Documentation
- [Getting Started](https://loglayer.dev/getting-started): Installation and basic usage
- [Configuration](https://loglayer.dev/configuration): All configuration options
- [Basic Logging](https://loglayer.dev/logging-api/basic-logging): Log levels, messages, prefixing, raw logging
- [Context](https://loglayer.dev/logging-api/context): Persistent context data
- [Metadata](https://loglayer.dev/logging-api/metadata): Per-message structured data
- [Error Handling](https://loglayer.dev/logging-api/error-handling): Error logging and serialization
- [Lazy Evaluation](https://loglayer.dev/logging-api/lazy-evaluation): Deferred computation for context and metadata
- [Child Loggers](https://loglayer.dev/logging-api/child-loggers): Creating inherited logger instances
- [Log Level Control](https://loglayer.dev/logging-api/adjusting-log-levels): Managing log levels
- [Groups](https://loglayer.dev/logging-api/groups): Route logs to specific transports by category
- [Transport Management](https://loglayer.dev/logging-api/transport-management): Dynamic transport control
- [TypeScript Tips](https://loglayer.dev/logging-api/typescript): TypeScript guidance
- [Testing / Mocking](https://loglayer.dev/logging-api/unit-testing): MockLogLayer for tests
- [Transport Overview](https://loglayer.dev/transports/): All available transports
- [Transport Configuration](https://loglayer.dev/transports/configuration): Common transport options
- [Creating Transports](https://loglayer.dev/transports/creating-transports): Build custom transports
- [Plugins Overview](https://loglayer.dev/plugins): Plugin system
- [Creating Plugins](https://loglayer.dev/plugins/creating-plugins): Build custom plugins
- [Context Managers](https://loglayer.dev/context-managers/): Context inheritance control
- [Log Level Managers](https://loglayer.dev/log-level-managers/): Log level inheritance control
- [Mixins](https://loglayer.dev/mixins/): Extend LogLayer with custom methods
## Optional
- [Creating Context Managers](https://loglayer.dev/context-managers/creating-context-managers): Build custom context managers
- [Creating Log Level Managers](https://loglayer.dev/log-level-managers/creating-log-level-managers): Build custom log level managers
- [Creating Mixins](https://loglayer.dev/mixins/creating-mixins): Build custom mixins
- [Testing Transports](https://loglayer.dev/transports/testing-transports): Test custom transports
- [Testing Plugins](https://loglayer.dev/plugins/testing-plugins): Test custom plugins
- [Testing Mixins](https://loglayer.dev/mixins/testing-mixins): Test custom mixins
- [ElysiaJS Integration](https://loglayer.dev/integrations/elysia): ElysiaJS plugin with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Express Integration](https://loglayer.dev/integrations/express): Express middleware with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Fastify Integration](https://loglayer.dev/integrations/fastify): Fastify integration with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Hono Integration](https://loglayer.dev/integrations/hono): Hono integration with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Koa Integration](https://loglayer.dev/integrations/koa): Koa middleware with request-scoped logging, optional `group` config to tag auto-logged messages with groups for routing/filtering
- [Next.js Integration](https://loglayer.dev/example-integrations/nextjs): Next.js example
- [Async Context Tracking](https://loglayer.dev/example-integrations/async-context): AsyncLocalStorage example
- [Bun Integration](https://loglayer.dev/example-integrations/bun): Bun example
- [Deno Integration](https://loglayer.dev/example-integrations/deno): Deno example