Logging Package

A comprehensive logging system powered by Clarity, providing structured logging, multiple output channels, log levels, file rotation, and debugging utilities.

Installation

bun add @stacksjs/logging

Basic Usage

import { log } from '@stacksjs/logging'

// Log messages at different levels
log.info('Application started')
log.success('User created successfully')
log.warn('API rate limit approaching')
log.error('Failed to connect to database')
log.debug('Processing request:', { userId: 123 })

Log Levels

Available Levels

import { log } from '@stacksjs/logging'

// Info - General information
log.info('Server listening on port 3000')

// Success - Successful operations
log.success('Build completed in 2.3s')

// Warning - Potential issues
log.warn('Deprecated API endpoint used')
log.warning('Memory usage above 80%') // Alias

// Error - Errors and failures
log.error('Database connection failed')
log.error(new Error('Something went wrong'))

// Debug - Detailed debugging information
log.debug('Request headers:', headers)
log.debug('SQL query:', query)

Log Level Configuration

# Set minimum log level
LOG_LEVEL=info

# Available levels: debug, info, warn, error
# debug shows all, error shows only errors
// Programmatic level setting
import { Logger } from '@stacksjs/logging'

const logger = new Logger('myapp', {
  level: 'debug' // 'debug' | 'info' | 'warn' | 'error'
})

Debugging Utilities

Dump

import { dump, log } from '@stacksjs/logging'

// Dump variables for debugging (doesn't stop execution)
dump(user)
dump({ name: 'John', roles: ['admin'] })

// Via log
log.dump(someObject)
log.dump(complexData, 'with label')

Dump and Die

import { dd } from '@stacksjs/logging'

// Dump and exit (stops execution)
dd(user)
// Program exits after this

// Via log
log.dd(object) // Logs and exits with code 1

Echo

import { echo } from '@stacksjs/logging'

// Simple echo (less verbose than info)
echo('Simple message')
echo(object) // Pretty prints objects

Structured Logging

Logging Objects

log.info('User action', {
  userId: 123,
  action: 'login',
  ip: '192.168.1.1',
  timestamp: new Date()
})

log.error('Request failed', {
  error: error.message,
  stack: error.stack,
  requestId: 'abc-123'
})

Multiple Arguments

// Multiple values are formatted and joined
log.info('Processing', itemCount, 'items')
log.debug('User:', user, 'Roles:', roles)

Timing Operations

Time Tracking

// Start a timer
const endTimer = log.time('database-query')

// Perform operation
await database.query(sql)

// End timer and log duration
await endTimer({ rows: results.length })
// Output: [database-query] 125ms { rows: 50 }

Manual Timing

const start = Date.now()

// Do work...

log.info(`Operation completed in ${Date.now() - start}ms`)

File Logging

Default Configuration

import { Logger } from '@stacksjs/logging'

const logger = new Logger('myapp', {
  level: 'info',
  logDirectory: 'storage/logs',
  writeToFile: true,
  fancy: true, // Pretty console output
  showTags: false
})

Log Files

Logs are written to the configured directory:

storage/logs/
  stacks-2024-01-15.log
  stacks-2024-01-16.log
  stacks-error-2024-01-15.log

File Format

[2024-01-15 10:30:45] INFO: Application started
[2024-01-15 10:30:46] SUCCESS: Database connected
[2024-01-15 10:30:47] ERROR: Failed to load config {"file":"app.config.ts"}

Error Handling Integration

Logging Errors

import { log } from '@stacksjs/logging'
import { handleError } from '@stacksjs/error-handling'

try {
  await riskyOperation()
} catch (error) {
  // Log error with context
  log.error(error, {
    context: 'riskyOperation',
    userId: currentUser.id
  })

  // Or use error handling integration
  handleError(error)
}

Error Options

log.error(error, {
  shouldExit: true,  // Exit process after logging
  silent: false,     // Show in console
  message: 'Custom error message'
})

Logger Configuration

Creating Custom Loggers

import { Logger } from '@stacksjs/logging'

// Create a named logger
const apiLogger = new Logger('api', {
  level: 'debug',
  logDirectory: 'storage/logs/api',
  writeToFile: true,
  fancy: true
})

// Use the logger
apiLogger.info('API request received')
apiLogger.debug('Request body:', body)

Logger Options

interface LoggerOptions {
  // Minimum log level
  level: 'debug' | 'info' | 'warn' | 'error'

  // Directory for log files
  logDirectory: string

  // Enable file writing
  writeToFile: boolean

  // Pretty console output
  fancy: boolean

  // Show category tags
  showTags: boolean
}

Context and Metadata

Adding Context

// Log with structured context
log.info('Order processed', {
  orderId: 'ORD-12345',
  customerId: 'CUST-67890',
  amount: 99.99,
  items: 3
})

// Error with context
log.error('Payment failed', {
  orderId: 'ORD-12345',
  error: paymentError.message,
  gateway: 'stripe'
})

Request Context

// In middleware or action
async function handle(request) {
  const requestId = crypto.randomUUID()

  log.info('Request received', {
    requestId,
    method: request.method,
    path: request.url,
    userAgent: request.headers.get('user-agent')
  })

  // ... handle request

  log.info('Response sent', {
    requestId,
    status: 200,
    duration: Date.now() - start
  })
}

Console Output

Fancy Mode

When fancy: true (default), logs are formatted with colors and symbols:

 INFO  Application started
 SUCCESS  Database connected  ✓
 WARN  Memory usage high
 ERROR  Connection failed  ✗
 DEBUG  Query executed in 15ms

Plain Mode

When fancy: false, simpler output:

[INFO] Application started
[SUCCESS] Database connected
[WARN] Memory usage high
[ERROR] Connection failed

Environment-Based Configuration

// Automatic configuration based on environment
const logger = new Logger('app', {
  // More verbose in development
  level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',

  // File logging in production
  writeToFile: process.env.NODE_ENV === 'production',

  // Fancy output in development
  fancy: process.env.NODE_ENV !== 'production'
})

Performance Logging

Measuring Performance

// Time a function
const timer = log.time('expensive-operation')

const result = await expensiveOperation()

await timer({ resultSize: result.length })

Query Logging

// Log database queries
log.debug('SQL Query', {
  query: sql,
  params: bindings,
  duration: queryTime
})

Log Rotation

Logs are automatically rotated daily:

storage/logs/
  stacks-2024-01-13.log
  stacks-2024-01-14.log
  stacks-2024-01-15.log  (current)

Custom Rotation

// Configure via Clarity
import { Logger } from '@stacksjs/clarity'

const logger = new Logger('app', {
  rotation: {
    maxSize: '10MB',
    maxFiles: 30,
    compress: true
  }
})

Edge Cases

Handling Circular References

// Objects with circular references are safely stringified
const obj = { name: 'test' }
obj.self = obj

log.info('Circular object:', obj)
// Output: Circular object: {"name":"test","self":"[Circular]"}

Handling Large Objects

// Large objects are truncated in console
const largeArray = new Array(10000).fill({ data: 'value' })
log.debug('Large array:', largeArray)
// Console shows truncated version, file has full log

Async Logging

// Logging is async but you usually don't need to await
log.info('This logs asynchronously')

// Await if you need to ensure log is written
await log.info('Important message')

Graceful Shutdown

// Ensure logs are flushed before exit
process.on('SIGTERM', async () => {
  await log.info('Shutting down...')
  // Wait for logs to flush
  await new Promise(resolve => setTimeout(resolve, 100))
  process.exit(0)
})

Best Practices

Structured Logging

// Good: Structured data
log.info('User action', { userId: 1, action: 'login' })

// Avoid: String concatenation
log.info(`User ${userId} performed action ${action}`)

Appropriate Log Levels

// DEBUG: Development details
log.debug('SQL query executed', { sql, duration })

// INFO: Business events
log.info('Order created', { orderId })

// WARN: Potential issues
log.warn('API deprecation', { endpoint, deadline })

// ERROR: Failures
log.error('Payment failed', { error, orderId })

Sensitive Data

// Don't log sensitive data
log.info('User login', {
  userId: user.id,
  email: user.email,
  // password: user.password  // Never log passwords!
})

// Mask sensitive fields
log.info('Payment processed', {
  cardLast4: card.number.slice(-4),
  // cardNumber: card.number  // Never log full card numbers!
})

API Reference

Log Methods

MethodDescription
log.info(...args)Info level message
log.success(msg)Success level message
log.warn(msg)Warning level message
log.warning(msg)Alias for warn
log.error(err, opts?)Error level message
log.debug(...args)Debug level message
log.dump(...args)Dump objects for debugging
log.dd(...args)Dump and die
log.echo(...args)Simple echo
log.time(label)Start timer, returns end function

Helper Functions

FunctionDescription
dump(...args)Dump objects
dd(...args)Dump and die
echo(...args)Echo output
logger()Get logger instance

Logger Class

MethodDescription
new Logger(name, opts)Create logger
logger.info(msg)Info message
logger.success(msg)Success message
logger.warn(msg)Warning message
logger.error(msg)Error message
logger.debug(msg)Debug message
logger.time(label)Timer function