# 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` 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('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 { 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 // combined metadata + context + error hasData?: boolean metadata?: Record // individual metadata error?: any // individual error context?: Record // 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 | 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 | null // Intercept withContext() calls onContextCalled?(context, loglayer): Record | 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): void appendContext(context: Record): void getContext(): Record 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 { recordMetric(name: string, value: number): T } declare module 'loglayer' { interface LogLayer extends IMetricsMixin {} interface MockLogLayer extends IMetricsMixin {} interface ILogLayer extends IMetricsMixin {} } // 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