Skip to main content
The Orion.js Logger is a powerful, flexible logging utility built on top of Winston. It provides structured logging capabilities with customizable formats, transports, and context tracking.

Installation

pnpm add @orion-js/logger

Basic Usage

import {logger} from '@orion-js/logger'

// Basic logging with different levels
logger.debug('Debug message', {customData: 'value'})
logger.info('Info message', {userId: 123})
logger.warn('Warning message', {attemptCount: 3})
logger.error('Error occurred', {error: new Error('Something went wrong')})

// Add metadata to all subsequent logs from this logger instance
const userLogger = logger.addMetadata({userId: 'user-123'})
userLogger.info('User action', {action: 'login'})

// Add context based on the current module
const contextLogger = logger.addContext(module)
contextLogger.info('Operation completed', {result: 'success'})

Critical Logging Pattern

The first argument must always be a static string. All variables should be passed in the object as the second argument.
// CORRECT - Static string first, variables in object
logger.info('User created successfully', {userId: user._id, email: user.email})
logger.warn('Rate limit approaching', {currentRate: rate, limit: maxRate})

// INCORRECT - Variables in the message string
logger.info(`User ${userId} created`) // DON'T DO THIS
logger.info('User ' + userId + ' created') // DON'T DO THIS
This pattern is required because:
  • Log aggregation tools can group logs by the static message
  • Searching logs by message becomes predictable
  • Variable data is properly indexed and queryable

Error Logging Pattern

When logging errors, the error object must be in a parameter named error in the second argument object:
try {
  await performOperation()
} catch (error) {
  // CORRECT - Error object in the 'error' property
  logger.error('Operation failed', {error, userId, operationType})

  // INCORRECT - Error as message or wrong property name
  logger.error(error.message) // DON'T DO THIS
  logger.error('Operation failed', {err: error}) // DON'T DO THIS
  logger.error('Operation failed: ' + error.message) // DON'T DO THIS
}
This ensures:
  • Stack traces are properly captured
  • Error serialization works correctly
  • Log analysis tools can extract error details

Features

Log Levels

The logger supports the standard log levels:
  • error: For error conditions
  • warn: For warning conditions
  • info: For informational messages
  • debug: For debugging information

Automatic Contextual Information

Each log message automatically includes:
  • Timestamp
  • Log level
  • File name where the log was triggered (automatically detected)
  • OpenTelemetry trace and span IDs (when available)

Formatting

The logger supports two main output formats:
  1. Text Format (for development):
    • Colorized output
    • Human-readable formatting
    • Activated when ORION_DEV=1 environment variable is set
  2. JSON Format (for production):
    • Structured JSON logs
    • Ideal for log aggregation and analysis
    • Default in production environments

OpenTelemetry Integration

The logger automatically detects and includes OpenTelemetry trace and span IDs when available, enabling correlation between logs and traces.

Advanced Configuration

Setting Log Level

import {setLogLevel} from '@orion-js/logger'

// Set the minimum log level to display
setLogLevel('info') // Will show info, warn, and error logs, but not debug

Adding Custom Transports

import {addTransport} from '@orion-js/logger'
import {transports} from 'winston'

// Add a file transport
const fileTransport = new transports.File({
  filename: 'application.log',
  level: 'info'
})

addTransport(fileTransport)

Complete Logger Configuration

import {configureLogger} from '@orion-js/logger'
import {format, transports} from 'winston'

configureLogger({
  level: 'debug',
  format: format.combine(
    format.timestamp(),
    format.json()
  ),
  transports: [
    new transports.Console(),
    new transports.File({filename: 'combined.log'})
  ]
})

Best Practices

  1. Use Static Message Strings: The first argument must be a static string for proper log aggregation:
    // Good
    logger.info('User created', {userId: '123', email: '[email protected]'})
    
    // Avoid
    logger.info(`User ${userId} created with email ${email}`)
    
  2. Pass Error Objects Correctly: Use the error property name:
    try {
      await someOperation()
    } catch (error) {
      // Good - preserves stack trace and uses correct property name
      logger.error('Operation failed', {error, context: 'user-service'})
    
      // Avoid
      logger.error('Operation failed', {message: error.message})
    }
    
  3. Add Context: Use logger.addContext(module) at the top of your files to automatically include file information.
  4. Use Appropriate Levels:
    • debug: Detailed information useful during development
    • info: Normal application behavior, milestones
    • warn: Unexpected but handled issues
    • error: Errors that prevent proper operation
  5. Include Relevant Data: Pass structured objects with relevant context:
    logger.info('Payment processed', {
      orderId: order._id,
      amount: payment.amount,
      currency: payment.currency,
      method: payment.method
    })
    

Common Patterns

Service Logging

@Service()
export class ProcessPaymentService {
  async execute(orderId: OrderId, paymentData: PaymentData) {
    logger.info('Processing payment', {orderId, amount: paymentData.amount})

    try {
      const result = await this.paymentGateway.charge(paymentData)
      logger.info('Payment successful', {orderId, transactionId: result.id})
      return result
    } catch (error) {
      logger.error('Payment failed', {error, orderId, amount: paymentData.amount})
      throw error
    }
  }
}

Migration Logging

@MigrationService({name: 'MigrateUsers.v1', useMongoTransactions: false})
export class MigrateUsers {
  async runMigration() {
    let processed = 0

    for await (const user of cursor) {
      await this.processUser(user)
      processed++

      // Log progress every 500 documents
      if (processed % 500 === 0) {
        logger.info('Migration progress', {processed, migrationName: 'MigrateUsers.v1'})
      }
    }

    logger.info('Migration complete', {processed, migrationName: 'MigrateUsers.v1'})
  }
}