Top SitesLogLayer: The modern logging library for Typescript / Javascript | LogLayer

Machine Readiness

Stored receipt and evidence

Overall

30

Readable

100

Callable

0

Commerce

0

Payment

0

Machine Access

Inspect the site's MCP endpoint

Open MCP explorer

DialtoneApp can scan the stored discovery files for this domain, try the MCP initialize handshake, and show the raw protocol transcript.

Purchase boundary

read only

Control boundary

unknown

Payment rails

None

Payment providers

None

Payment methods

None

Payment protocols

None

Payment assets

None

Payment networks

None

Capabilities

None

Verified payment surface

No

Crypto only

No

Readable docs

robots, llms, llms-full

Products

0

Variants

0

Priced variants

0

Currencies

0

Offers

0

Priced offers

0

Priced actions

0

Samples

Offer samples

No stored offer samples.

Samples

Action samples

No stored action samples.

Samples

Product samples

No stored product samples.

Document

robots.txt

Open robots.txt
User-agent: *
Allow: /

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

Document

llms.txt

Open llms.txt
# LogLayer

> A structured logging library with a fluent API for Typescript / Javascript. It separates log data into context (persistent), metadata (per-message), and errors with support for 25+ transports and plugins.

## Installation

```
npm install loglayer
```

## Quick Start

```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

llms-full.txt

Open llms-full.txt
# LogLayer

> A structured logging library with a fluent API for Typescript / Javascript. It separates log data into context (persistent), metadata (per-message), and errors with support for 25+ transports and plugins.

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