Lockness Logger
Lockness Logger
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 informationINFO(1) - General informational messagesWARN(2) - Warning messagesERROR(3) - Error conditionsFATAL(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')
})