# LogLayer: The modern logging library for Typescript / Javascript | LogLayer

> Markdown mirror of DialtoneApp's public top-site detail page for `loglayer.dev`.

URL: https://dialtoneapp.com/top-sites/loglayer.dev/index.md
Canonical HTML: https://dialtoneapp.com/top-sites/loglayer.dev

## Summary

- Domain: `loglayer.dev`
- Website: https://loglayer.dev
- Description: ai readable | score 30 | purchase read only
- Label: ai_readable
- Payment surface: Not available
- Purchase boundary: read_only
- Control boundary: unknown
- Rank: 628

## robots

~~~text
User-agent: *
Allow: /

Sitemap: https://loglayer.dev/sitemap.xml
~~~

## llms

~~~text
# 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
~~~

## llms-full

~~~text
# 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
~~~