Lockness Logger

Lockness Logger

VIEW

Structured logging system with multiple levels, transports, and formatters for production-ready applications.

Overview

@lockness/logger provides:

  • 5 Log Levels - DEBUG, INFO, WARN, ERROR, FATAL with filtering
  • Multiple Formatters - Text, JSON, Pretty (with colors)
  • Flexible Transports - Console, File, Memory, Custom
  • Contextual Logging - Child loggers with inherited context
  • Metadata Support - Attach structured data to logs
  • Async Operations - Non-blocking logging
  • Type Safe - Full TypeScript support

Installation

typescript
import { configureLogger, Logger } from '@lockness/logger'
// or
import { configureLogger, Logger } from '@lockness/core'

Basic Usage

Simple Logging

typescript
import { Logger } from '@lockness/logger'

const log = new Logger()

await log.info('Application started')
await log.warn('Low disk space', { available: '10GB' })
await log.error('Database connection failed', new Error('Connection timeout'))

Global Logger

typescript
import { configureLogger, logger } from '@lockness/logger'

// Configure once at startup
configureLogger({
    level: LogLevel.DEBUG,
})

// Use anywhere in your app
await logger().info('User logged in', { userId: 123 })

Quick Helpers

typescript
import { error, info, warn } from '@lockness/logger'

await info('Server started on port 3000')
await warn('Rate limit approaching')
await error('Failed to send email', new Error('SMTP error'))

Log Levels

Logs are filtered by severity threshold:

typescript
import { Logger, LogLevel } from '@lockness/logger'

const log = new Logger({ level: LogLevel.WARN })

await log.debug('Debug message') // Skipped
await log.info('Info message') // Skipped
await log.warn('Warning message') // Logged
await log.error('Error message') // Logged
await log.fatal('Fatal message') // Logged

Level Hierarchy:

  • DEBUG (0) - Detailed debugging information
  • INFO (1) - General informational messages
  • WARN (2) - Warning messages
  • ERROR (3) - Error conditions
  • FATAL (4) - Critical failures requiring immediate attention

Formatters

Text Formatter (Production Default)

typescript
import { Logger, TextFormatter } from '@lockness/logger'

const log = new Logger({
    formatter: new TextFormatter(),
})

await log.info('User login', { userId: 123 })
// Output: 2024-01-01T12:00:00.000Z INFO  User login {"userId":123}

JSON Formatter (Log Aggregation)

Perfect for log aggregation systems (ELK, Splunk, etc.):

typescript
import { JsonFormatter, Logger } from '@lockness/logger'

const log = new Logger({
    formatter: new JsonFormatter(),
})

await log.info('API request', { method: 'GET', path: '/users' })
// Output: {"timestamp":"2024-01-01T12:00:00.000Z","level":"INFO","message":"API request","method":"GET","path":"/users"}

Pretty Formatter (Development)

Colorized output for development:

typescript
import { Logger, PrettyFormatter } from '@lockness/logger'

const log = new Logger({
    formatter: new PrettyFormatter(),
})

await log.info('Server ready')
// Output: ℹ️  12:00:00 INFO  Server ready (with colors!)

Transports

Console Transport

Log to stdout/stderr:

typescript
import { ConsoleTransport, Logger } from '@lockness/logger'

const log = new Logger({
    transports: [new ConsoleTransport()],
})

await log.info('Console output')

File Transport

Log to files:

typescript
import { FileTransport, Logger, TextFormatter } from '@lockness/logger'

const log = new Logger({
    transports: [
        new FileTransport('./logs/app.log', new TextFormatter()),
    ],
})

await log.info('Written to file')

// Close file handles when done
await log.close()

Memory Transport (Testing)

Store logs in memory for testing:

typescript
import { Logger, MemoryTransport } from '@lockness/logger'

const memory = new MemoryTransport()
const log = new Logger({
    transports: [memory],
})

await log.info('Test message')

const logs = memory.getLogs()
console.log(logs) // [{ level: 1, message: '...', ... }]

memory.clear() // Clear logs

Multiple Transports

Log to multiple destinations simultaneously:

typescript
import {
    ConsoleTransport,
    FileTransport,
    Logger,
    PrettyFormatter,
    TextFormatter,
} from '@lockness/logger'

const log = new Logger({
    transports: [
        new ConsoleTransport(), // Pretty console output
        new FileTransport('./logs/app.log', new TextFormatter()),
    ],
    formatter: new PrettyFormatter(),
})

await log.info('Logged everywhere')

Contextual Logging

Create child loggers with inherited context:

typescript
import { Logger } from '@lockness/logger'

const appLog = new Logger({ context: 'App' })

const dbLog = appLog.child('Database')
const cacheLog = appLog.child('Cache')

await dbLog.info('Query executed')
// Output: ... [App:Database] Query executed

await cacheLog.info('Cache hit')
// Output: ... [App:Cache] Cache hit

Nested contexts:

typescript
const serverLog = new Logger({ context: 'Server' })
const apiLog = serverLog.child('API')
const userLog = apiLog.child('Users')

await userLog.info('User created')
// Output: ... [Server:API:Users] User created

Metadata

Attach structured data to logs:

typescript
await log.info('User action', {
    userId: 123,
    action: 'login',
    ip: '127.0.0.1',
    timestamp: Date.now(),
})

Error objects are automatically expanded:

typescript
try {
    throw new Error('Database connection failed')
} catch (err) {
    await log.error('Connection error', err)
    // Includes: error message, stack trace, error name
}

Custom error metadata:

typescript
await log.error('Request failed', {
    statusCode: 500,
    endpoint: '/api/users',
    duration: 1234,
    userId: 'abc123',
})

API Reference

Logger Class

typescript
// Constructor
new Logger(config?: LoggerConfig)

// Configuration
interface LoggerConfig {
    level?: LogLevel              // Minimum log level (default: INFO)
    transports?: LogTransport[]   // Output destinations
    formatter?: LogFormatter      // Log format
    context?: string              // Logger context name
}

// Methods
setLevel(level: LogLevel): void
getLevel(): LogLevel
addTransport(transport: LogTransport): void
child(context: string): Logger

// Logging methods
debug(message: string, metadata?: Record<string, unknown>): Promise<void>
info(message: string, metadata?: Record<string, unknown>): Promise<void>
warn(message: string, metadata?: Record<string, unknown>): Promise<void>
error(message: string, error?: Error | Record<string, unknown>): Promise<void>
fatal(message: string, error?: Error | Record<string, unknown>): Promise<void>

// Cleanup
close(): Promise<void>

Global Functions

typescript
configureLogger(config?: LoggerConfig): Logger
logger(): Logger
createLogger(config?: LoggerConfig): Logger

debug(message: string, metadata?: Record<string, unknown>): Promise<void>
info(message: string, metadata?: Record<string, unknown>): Promise<void>
warn(message: string, metadata?: Record<string, unknown>): Promise<void>
error(message: string, error?: Error | Record<string, unknown>): Promise<void>
fatal(message: string, error?: Error | Record<string, unknown>): Promise<void>

Use Cases

HTTP Request Logging

typescript
import { JsonFormatter, Logger } from '@lockness/logger'

const httpLog = new Logger({
    context: 'HTTP',
    formatter: new JsonFormatter(),
})

// Log requests
await httpLog.info('Request received', {
    method: 'POST',
    path: '/api/users',
    ip: '192.168.1.1',
    userAgent: 'Mozilla/5.0...',
})

// Log responses
await httpLog.info('Response sent', {
    status: 201,
    duration: 45,
    bytes: 1024,
})

Service-Specific Logging

typescript
import { Logger } from '@lockness/logger'

const appLog = new Logger({ context: 'App' })

// Database logger
const dbLog = appLog.child('DB')
await dbLog.info('Connected to PostgreSQL')
await dbLog.debug('Query', { sql: 'SELECT * FROM users', duration: 23 })

// Cache logger
const cacheLog = appLog.child('Cache')
await cacheLog.info('Redis connected')
await cacheLog.debug('Cache hit', { key: 'user:123' })

// Queue logger
const queueLog = appLog.child('Queue')
await queueLog.info('Worker started')
await queueLog.warn('Queue backlog', { count: 1000 })

Development vs Production

typescript
import {
    ConsoleTransport,
    FileTransport,
    JsonFormatter,
    Logger,
    LogLevel,
    PrettyFormatter,
} from '@lockness/logger'

const isDev = Deno.env.get('ENV') === 'development'

const log = new Logger({
    level: isDev ? LogLevel.DEBUG : LogLevel.INFO,
    formatter: isDev ? new PrettyFormatter() : new JsonFormatter(),
    transports: isDev ? [new ConsoleTransport()] : [
        new FileTransport('./logs/app.log'),
        new ConsoleTransport(),
    ],
})

Application Lifecycle

typescript
import { Logger, LogLevel } from '@lockness/logger'

const log = new Logger({
    level: LogLevel.INFO,
    context: 'MyApp',
})

// Application startup
await log.info('Application starting', {
    version: '1.0.0',
    env: Deno.env.get('ENV'),
    node: Deno.version.deno,
})

await log.info('Database connected', {
    host: 'localhost',
    database: 'myapp',
})

await log.info('Server listening', {
    port: 3000,
    mode: 'production',
})

// User actions
await log.info('User registered', {
    userId: 'abc123',
    email: 'user@example.com',
    timestamp: new Date(),
})

// Errors
await log.error('Payment failed', {
    userId: 'abc123',
    amount: 99.99,
    gateway: 'stripe',
    errorCode: 'card_declined',
})

Best Practices

Use Appropriate Log Levels

typescript
await log.debug('Variable value:', { value: x }) // Development only
await log.info('User logged in') // Informational
await log.warn('Cache miss') // Warnings
await log.error('Failed to save', err) // Errors
await log.fatal('Database unavailable', err) // Critical

Add Context to Loggers

typescript
const userLog = appLog.child('UserService')
const orderLog = appLog.child('OrderService')

Include Metadata

typescript
await log.info('Order placed', {
    orderId: 'ORD-123',
    userId: 456,
    total: 99.99,
    items: 3,
})

Log Errors with Stack Traces

typescript
try {
    await operation()
} catch (err) {
    await log.error('Operation failed', err as Error)
}

Use JSON Format for Production

typescript
import { JsonFormatter } from '@lockness/logger'

const log = new Logger({
    formatter: new JsonFormatter(),
})

Close File Transports

typescript
const log = new Logger({
    transports: [new FileTransport('./app.log')],
})

// ... application logic ...

await log.close() // Close file handles

Configure Once, Use Everywhere

typescript
// app.ts
configureLogger({
    level: LogLevel.INFO,
    formatter: new JsonFormatter(),
})

// any-file.ts
import { logger } from '@lockness/logger'
await logger().info('Ready')

Custom Transport

Create custom transports for third-party services:

typescript
import type { LogEntry, LogTransport } from '@lockness/logger'

class SlackTransport implements LogTransport {
    constructor(private webhookUrl: string) {}

    async log(entry: LogEntry): Promise<void> {
        if (entry.level >= LogLevel.ERROR) {
            await fetch(this.webhookUrl, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    text: `[${entry.level}] ${entry.message}`,
                    attachments: [{ text: JSON.stringify(entry.metadata) }],
                }),
            })
        }
    }
}

const log = new Logger({
    transports: [
        new ConsoleTransport(),
        new SlackTransport('https://hooks.slack.com/...'),
    ],
})

Performance

  • Logs below threshold skipped (no formatting overhead)
  • Async operations don't block application
  • Formatting happens once, sent to all transports
  • Memory transport is fast for testing

Testing

typescript
import { Logger, MemoryTransport } from '@lockness/logger'

Deno.test('test logging', async () => {
    const memory = new MemoryTransport()
    const log = new Logger({ transports: [memory] })

    await log.info('Test message', { foo: 'bar' })

    const logs = memory.getLogs()
    assertEquals(logs.length, 1)
    assertEquals(logs[0].message, 'Test message')
    assertEquals(logs[0].metadata.foo, 'bar')
})