Introducing Socket Firewall: Free, Proactive Protection for Your Software Supply Chain.Learn More
Socket
Book a DemoInstallSign in
Socket

@analog-tools/logger

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@analog-tools/logger

Logging utility for AnalogJS applications

latest
Source
npmnpm
Version
0.0.9
Version published
Maintainers
1
Created
Source

@analog-tools/logger

⚠️ IMPORTANT: Early Development Stage ⚠️
This project is in its early development stage. Breaking changes may happen frequently as the APIs evolve. Use with caution in production environments.

A minimal, type-safe logging utility for server-side applications in AnalogJS, Nitro and H3-based environments. Works standalone or with optional @analog-tools/inject integration for dependency injection patterns.

npm version License: MIT npm downloads TypeScript

Table of Contents

Features

  • 📝 Type-safe log levels with LogLevel union type: trace, debug, info, warn, error, fatal, silent
  • 🛡️ Compile-time type checking for log level configuration
  • ⚠️ Runtime validation with graceful fallback for invalid log levels
  • 🎯 IntelliSense support for log level auto-completion
  • 🎨 Metadata-based styling with colors, formatting, and emoji icons
  • 🌈 Curated color palette with rich ANSI color support via ColorEnum
  • Semantic styling with global and per-call configuration
  • 🧩 Seamless integration with @analog-tools/inject for dependency injection
  • 🌳 Context-based logging with child loggers
  • 🔧 Configurable via environment variables
  • 🚫 Context filtering with disabledContexts
  • 🌐 Nitro middleware and request handler integration
  • 🧪 Mock logger implementation for testing
  • 🚀 Lightweight implementation using standard console
  • 🔥 Enhanced Error Handling with multiple overloads and type safety
  • 🛡️ Structured Error Serialization with circular reference protection
  • 📊 LogContext Support for structured logging
  • 🔄 Backwards Compatibility with existing error logging patterns
  • 🔁 Smart Log Deduplication with aggregation to reduce noise from repeated messages

Prerequisites

  • Node.js 18.13.0 or later
  • AnalogJS project or Nitro/H3-based application
  • TypeScript 4.8 or later (for full type safety)
  • @analog-tools/inject ^0.0.5 (peer dependency)

Compatibility Matrix

@analog-tools/loggerAnalogJSNode.jsTypeScript
0.0.5≥ 1.0.0≥ 18.13≥ 4.8

Installation

# Using npm
npm install @analog-tools/logger

# Using pnpm
pnpm add @analog-tools/logger

# Using yarn
yarn add @analog-tools/logger

30-Second Quickstart

Get up and running with @analog-tools/logger in under a minute:

import { LoggerService } from '@analog-tools/logger';

// Create a logger instance
const logger = new LoggerService({ level: 'info', name: 'my-app' });

// Start logging immediately
logger.info('Hello from @analog-tools/logger!');
logger.warn('This is a warning message');
logger.error('Error occurred', new Error('Something went wrong'));

// Add styling and icons with metadata
logger.info('Success!', { style: 'success', icon: '✅' });
logger.warn('Be careful', { icon: '⚠️' });
logger.error('Critical error', { 
  style: { color: ColorEnum.FireRed, bold: true },
  icon: '🔥' 
});

// Create context-specific loggers
const dbLogger = logger.forContext('database');
dbLogger.info('Database operation completed', { icon: '🗄️' });

Quick Start

Here's a basic example of using the logger:

import { LoggerService } from '@analog-tools/logger';

// Create a logger instance
const logger = new LoggerService({
  level: 'info',
  name: 'my-app',
});

// Log at different levels
logger.debug('Debug message');
logger.info('Info message', { userId: 123 });
logger.warn('Warning message');
logger.error('Error occurred', new Error('Something went wrong'));
logger.fatal('Fatal error', new Error('Critical failure'));

// Create a context-specific logger
const authLogger = logger.forContext('auth');
authLogger.info('User authenticated');

// Group related log messages
logger.group('API Request');
logger.info('Processing request to /users');
logger.debug('Validating request body');
logger.info('Request processed successfully');
logger.groupEnd('API Request');

// Nested groups
logger.group('Database Operations');
logger.info('Starting transaction');

logger.group('Query Execution');
logger.debug('Executing SQL: SELECT * FROM users');
logger.debug('Query completed in 15ms');
logger.groupEnd('Query Execution');

logger.info('Transaction committed');
logger.groupEnd('Database Operations');

Type Safety

@analog-tools/logger provides comprehensive type safety through TypeScript, ensuring compile-time validation and excellent developer experience.

LogLevelString Type

The logger uses a string union type LogLevel for log levels, providing compile-time validation and IntelliSense support:

import { LoggerConfig, LogLevel, isValidLogLevel } from '@analog-tools/logger';

// ✅ Valid log levels with IntelliSense support
const validLevels: LogLevel[] = [
  'trace', 'debug', 'info', 'warn', 'error', 'fatal', 'silent'
];

// ✅ Type-safe configuration
const config: LoggerConfig = {
  level: 'debug', // ← IntelliSense shows valid options
  name: 'my-app'
};

// ❌ TypeScript error for invalid levels
const invalidConfig: LoggerConfig = {
  level: 'verbose', // ← TypeScript error: Type '"verbose"' is not assignable
  name: 'my-app'
};

Runtime Validation

The logger provides graceful runtime validation for scenarios where log levels come from external sources:

import { LoggerService, isValidLogLevel } from '@analog-tools/logger';

// Runtime validation function
if (isValidLogLevel(externalLevel)) {
  // Safe to use
  const logger = new LoggerService({ level: externalLevel });
} else {
  // Handle invalid level
  console.warn(`Invalid log level: ${externalLevel}`);
}

// Automatic fallback with warning
const logger = new LoggerService({ 
  level: 'INVALID' as LogLevel, // Runtime error
  name: 'my-app' 
});
// Console output: [LoggerService] Invalid log level "INVALID". Falling back to "info"...

LogLevel Enum

For scenarios where you need numeric log level values:

import { LogLevelEnum } from '@analog-tools/logger';

console.log(LogLevelEnum.trace);  // 0
console.log(LogLevelEnum.debug);  // 1
console.log(LogLevelEnum.info);   // 2
console.log(LogLevelEnum.warn);   // 3
console.log(LogLevelEnum.error);  // 4
console.log(LogLevelEnum.fatal);  // 5
console.log(LogLevelEnum.silent); // 6

// Useful for custom log level comparisons
if (currentLogLevel >= LogLevelEnum.warn) {
  // Log warning and above
}

Type-Safe Nitro Integration

The Nitro integration also supports type-safe log levels:

import { withLogging, LogLevel } from '@analog-tools/logger';

// ✅ Type-safe Nitro middleware
export default withLogging(myHandler, {
  namespace: 'api',
  level: 'debug' as LogLevel, // ← IntelliSense support
  logResponse: true
});

// ❌ TypeScript error for invalid levels
export default withLogging(myHandler, {
  level: 'verbose' // ← TypeScript error
});

Migration from String Types

If you're migrating from a version that used generic string types:

// Before (generic string)
interface OldConfig {
  level?: string; // ❌ No type safety
}

// After (type-safe)
interface NewConfig {
  level?: LogLevel; // ✅ Compile-time validation
}

// Migration strategy
const config: LoggerConfig = {
  level: process.env.LOG_LEVEL as LogLevel, // Runtime validation will handle invalid values
  name: 'my-app'
};

Usage with @analog-tools/inject (Optional)

The logger works perfectly standalone, but integrates seamlessly with @analog-tools/inject for dependency injection patterns. Note: @analog-tools/inject is a peer dependency and completely optional.

Standalone Usage (No Injection Framework)

import { LoggerService } from '@analog-tools/logger';

// Direct instantiation - works without @analog-tools/inject
const logger = new LoggerService({
  level: 'debug',
  name: 'my-app',
  disabledContexts: ['verbose-module']
});

// Use directly in your services
class UserService {
  private logger = new LoggerService({ name: 'user-service' });
  private userLogger = this.logger.forContext('users');

  getUser(id: string) {
    this.userLogger.info(`Getting user with id ${id}`, { userId: id });
    // Implementation...
  }
}

// Or create a singleton pattern
class LoggerFactory {
  private static instance: LoggerService;
  
  static getInstance(): LoggerService {
    if (!this.instance) {
      this.instance = new LoggerService({
        level: process.env['LOG_LEVEL'] || 'info',
        name: 'my-app'
      });
    }
    return this.instance;
  }
}

// Usage
const logger = LoggerFactory.getInstance();
const apiLogger = logger.forContext('api');

With @analog-tools/inject Integration

When you want centralized dependency injection and configuration:

import { inject, registerService } from '@analog-tools/inject';
import { LoggerService } from '@analog-tools/logger';

// Register with custom configuration (optional)
registerService(LoggerService, {
  level: 'debug',
  name: 'my-app',
  disabledContexts: ['verbose-module'] // Disable specific contexts
});

// Inject in your services or API routes
class UserService {
  private logger = inject(LoggerService);
  private userLogger = this.logger.forContext('users');

  getUser(id: string) {
    this.userLogger.info(`Getting user with id ${id}`, { userId: id });
    // Implementation...
  }
}

// Auto-registration: If not manually registered, LoggerService will be auto-registered with defaults
class ProductService {  
  // This works even without explicit registerService() call
  private logger = inject(LoggerService).forContext('products');
  
  getProduct(id: string) {
    this.logger.info(`Fetching product ${id}`);
    // Implementation...
  }
}

Choosing Between Approaches

Use Standalone When:

  • You prefer direct control over logger instances
  • You're not using dependency injection patterns
  • You want minimal dependencies
  • You're integrating into existing applications without DI

Use with @analog-tools/inject When:

  • You want centralized configuration management
  • You're building applications with dependency injection patterns
  • You want consistent logger configuration across services
  • You're using other @analog-tools packages that leverage injection

Migration Between Approaches

You can easily switch between standalone and injection patterns:

  • To Standalone: Replace inject(LoggerService) with new LoggerService()
  • To Injection: replace direct instantiation with inject(LoggerService)
  • Mixed Approach: Use injection for services and standalone for utilities as needed

Usage in AnalogJS API Routes

Basic Usage (With @analog-tools/inject)

// src/server/routes/api/users/[id].ts
import { defineEventHandler, getRouterParam } from 'h3';
import { inject } from '@analog-tools/inject';
import { LoggerService } from '@analog-tools/logger';

export default defineEventHandler((event) => {
  const logger = inject(LoggerService).forContext('users-api');
  const userId = getRouterParam(event, 'id');

  logger.info(`User endpoint called for ID: ${userId}`, {
    userId,
    path: event.node.req.url,
  });

  // Route implementation...
  return { id: userId, name: 'Example User' };
});

Basic Usage (Standalone)

// src/server/routes/api/users/[id].ts
import { defineEventHandler, getRouterParam } from 'h3';
import { LoggerService } from '@analog-tools/logger';

// Create a shared logger instance (you might want to create this in a separate module)
const apiLogger = new LoggerService({ 
  level: 'info', 
  name: 'api' 
});

export default defineEventHandler((event) => {
  const logger = apiLogger.forContext('users-api');
  const userId = getRouterParam(event, 'id');

  logger.info(`User endpoint called for ID: ${userId}`, {
    userId,
    path: event.node.req.url,
  });

  // Route implementation...
  return { id: userId, name: 'Example User' };
});

Shared Logger Configuration for API Routes

For standalone usage, create a shared logger module:

// src/server/utils/logger.ts
import { LoggerService } from '@analog-tools/logger';

export const apiLogger = new LoggerService({
  level: process.env['LOG_LEVEL'] || 'info',
  name: 'api',
  disabledContexts: process.env['LOG_DISABLED_CONTEXTS']?.split(',') || []
});

// Export commonly used context loggers
export const usersApiLogger = apiLogger.forContext('users');
export const productsApiLogger = apiLogger.forContext('products');
export const authApiLogger = apiLogger.forContext('auth');
// src/server/routes/api/users/[id].ts
import { defineEventHandler, getRouterParam } from 'h3';
import { usersApiLogger } from '../../utils/logger';

export default defineEventHandler((event) => {
  const userId = getRouterParam(event, 'id');

  usersApiLogger.info(`User endpoint called for ID: ${userId}`, {
    userId,
    path: event.node.req.url,
  });

  // Route implementation...
  return { id: userId, name: 'Example User' };
});

With Nitro Middleware

// src/server/middleware/logging.ts
import { createLoggerMiddleware } from '@analog-tools/logger';

export default createLoggerMiddleware('api-requests');

With Request Handler Wrapper

// src/server/routes/api/products/index.ts
import { defineEventHandler } from 'h3';
import { withLogging } from '@analog-tools/logger';

export default withLogging(
  defineEventHandler(() => {
    // Handler implementation
    return { products: [] };
  }),
  { 
    namespace: 'products-api', 
    level: 'info',
    logResponse: true // Log response body (use with caution)
  }
);

Context-Based Logging

Create child loggers for specific contexts:

// Main logger
const logger = new LoggerService();

// Create context-specific loggers
const authLogger = logger.forContext('auth');
const dbLogger = logger.forContext('database');

// Usage
authLogger.info('User logged in'); // Logs with context: 'auth'
dbLogger.error('Database connection failed'); // Logs with context: 'database'

Disabling Specific Contexts

You can disable logging for specific contexts:

// Option 1: Via constructor configuration
const logger = new LoggerService({
  disabledContexts: ['verbose-module', 'debug-info']
});

// Option 2: Via setter method
logger.setDisabledContexts(['verbose-module', 'debug-info']);

// Option 3: Via environment variable
// Set LOGGER_DISABLED_CONTEXTS=verbose-module,debug-info

API Reference

LoggerService

The main logger class:

class LoggerService implements ILogger {
  // Mark as injectable for @analog-tools/inject
  static INJECTABLE = true;

  constructor(config?: LoggerConfig);
  
  // Create a child logger with context
  forContext(context: string): ILogger;
  
  // Get/set configuration
  getLogLevel(): LogLevel;
  getDisabledContexts(): string[];
  setDisabledContexts(contexts: string[]): void;
  setUseColors(enabled: boolean): void;
  getUseColors(): boolean;
  
  // Logging methods
  trace(message: string, ...data: unknown[]): void;
  debug(message: string, ...data: unknown[]): void;
  info(message: string, ...data: unknown[]): void;
  warn(message: string, ...data: unknown[]): void;
  error(message: string, error?: Error | unknown, ...data: unknown[]): void;
  fatal(message: string, error?: Error | unknown, ...data: unknown[]): void;
}

LoggerConfig

Configuration options:

interface LoggerConfig {
  // Log level (default: 'info' or from LOG_LEVEL env variable)
  level?: string; 
  
  // Logger name prefix (default: 'analog-tools')
  name?: string; 

  // Contexts to disable logging for
  disabledContexts?: string[];
  
  // Whether to use colored output (default: true in non-test environments)
  useColors?: boolean;
}

Nitro Integration

// Create middleware that adds logger to event context
createLoggerMiddleware(namespace: string = 'api'): EventHandler;

// Wrap an event handler with automatic request logging
withLogging<T extends EventHandlerRequest>(
  handler: EventHandler<T>,
  options?: {
    namespace?: string;    // Context for the logger (default: 'api')
    level?: 'debug' | 'info'; // Log level (default: 'debug')
    logResponse?: boolean; // Whether to log response bodies (default: false)
  }
): EventHandler<T>;

Mock Logger for Testing

class MockLoggerService implements ILogger {
  static INJECTABLE = true;
  
  // All standard logger methods that can be spied on in tests
  forContext(context: string): ILogger;
  trace(message: string, data?: Record<string, unknown>): void;
  debug(message: string, data?: Record<string, unknown>): void;
  info(message: string, data?: Record<string, unknown>): void;
  warn(message: string, data?: Record<string, unknown>): void;
  error(message: string, error?: Error | unknown, data?: Record<string, unknown>): void;
  fatal(message: string, error?: Error | unknown, data?: Record<string, unknown>): void;
}

Environment Variables

The logger supports the following environment variables:

LOG_LEVEL=info              # Set the default log level
LOG_DISABLED_CONTEXTS=verbose-module,debug-info  # Comma-separated list of contexts to disable

Testing with MockLoggerService

For unit tests, you can use the provided MockLoggerService:

import { MockLoggerService } from '@analog-tools/logger';
import { registerService } from '@analog-tools/inject';
import { beforeEach, describe, expect, it, vi } from 'vitest';

describe('MyService', () => {
  let mockLogger: MockLoggerService;
  
  beforeEach(() => {
    mockLogger = new MockLoggerService();
    registerService(LoggerService, mockLogger);
    
    // Spy on logger methods
    vi.spyOn(mockLogger, 'info');
    vi.spyOn(mockLogger, 'error');
  });
  
  it('should log correctly', () => {
    const myService = new MyService();
    myService.doSomething();
    
    expect(mockLogger.info).toHaveBeenCalledWith(
      'Operation completed', 
      expect.objectContaining({ status: 'success' })
    );
  });
});

Metadata-Based Styling and Icons

@analog-tools/logger now supports a powerful metadata-based styling and icon system for all log methods. This enables semantic and custom styles, emoji icons, and per-call or global configuration for beautiful, expressive logs.

Usage Example

import { LoggerService, ColorEnum } from '@analog-tools/logger';

const logger = new LoggerService({
  level: 'info',
  name: 'my-app',
  // Optional: global style/icon config
  styles: { highlight: { color: ColorEnum.LemonYellow, bold: true } },
  icons: { success: '✅', info: 'ℹ️' }
});

// Per-call metadata for style and icon
logger.info('Success!', { style: 'success', icon: '✅' });
logger.warn('Be careful', { icon: '⚠️' });
logger.info('Custom dreamy color', { style: { color: ColorEnum.DeepPurple, underline: true } });
logger.info('With emoji', { icon: '🚀' });

Supported Features

  • Semantic styles: Use names like 'success', 'warning', 'highlight', etc.
  • Custom styles: Use ColorEnum and style config for color, bold, underline, background, etc.
  • Emoji icons: Use any emoji or semantic icon name (e.g., 'success', 'info').
  • Global config: Set default styles/icons in LoggerConfig.
  • Per-call override: Pass metadata as the last argument to any log method.
  • Fallback/warning: Unknown styles/icons trigger a warning and fallback to defaults.

Example: Highlighted Info (replaces info2())

// Old:
// logger.info2('Important!');
// New:
logger.info('Important!', { style: 'highlight', icon: '⭐️' });

See the Migration Guide for upgrade instructions and more examples.

Use predefined semantic styles for common scenarios:

// Configure semantic styles globally
const logger = new LoggerService({
  level: 'info',
  name: 'my-app',
  useColors: true,
  styles: {
    highlight: { color: ColorEnum.LemonYellow, bold: true },
    success: { color: ColorEnum.ForestGreen },
    error: { color: ColorEnum.FireRed },
    warning: { color: ColorEnum.TangerineOrange },
    info: { color: ColorEnum.OceanBlue },
    debug: { color: ColorEnum.SlateGray }
  },
  icons: {
    success: '✅',
    warning: '⚠️',
    error: '❌',
    info: 'ℹ️',
    debug: '🐞'
  }
});

// Use semantic styles
logger.info('Important message', { style: 'highlight' });
logger.info('Operation successful', { style: 'success', icon: 'success' });
logger.info('Debug information', { style: 'debug', icon: 'debug' });

ColorEnum Options

The logger includes a comprehensive set of dreamy colors with ANSI codes:

// Blue shades
ColorEnum.SkyBlue        // Bright blue
ColorEnum.OceanBlue      // Standard blue  
ColorEnum.MidnightBlue   // Deep blue

// Green shades
ColorEnum.MintGreen      // Bright green
ColorEnum.ForestGreen    // Standard green
ColorEnum.EmeraldGreen   // Deep green

// Yellow shades
ColorEnum.LemonYellow    // Bright yellow
ColorEnum.SunflowerYellow // Standard yellow
ColorEnum.GoldYellow     // Gold

// Red shades
ColorEnum.RoseRed        // Bright red
ColorEnum.FireRed        // Standard red
ColorEnum.BurgundyRed    // Deep red

// Purple shades
ColorEnum.LavenderPurple // Bright purple
ColorEnum.RoyalPurple    // Medium purple
ColorEnum.DeepPurple     // Deep purple

// Orange shades
ColorEnum.PeachOrange    // Light orange
ColorEnum.TangerineOrange // Standard orange
ColorEnum.AmberOrange    // Deep orange

// Gray shades
ColorEnum.SilverGray     // Light gray
ColorEnum.SlateGray      // Medium gray
ColorEnum.CharcoalGray   // Dark gray

// Background colors (add "Bg" suffix)
ColorEnum.SkyBlueBg      // Blue background
ColorEnum.ForestGreenBg  // Green background
// ... and many more

Advanced Styling Options

// Custom styling with multiple format options
logger.info('Formatted message', {
  style: { 
    color: ColorEnum.RoyalPurple,
    bold: true,
    underline: true 
  },
  icon: '🎨'
});

// Using with regular data
logger.info('User logged in', 
  { userId: '123', timestamp: Date.now() },
  { style: 'success', icon: '👤' }
);

// Multiple data objects with metadata
logger.info('Complex operation',
  { step: 1, status: 'processing' },
  { duration: 150, memory: '2.1MB' },
  { style: 'highlight', icon: '⚡️' }
);

Error and Fatal Logging with Metadata

The enhanced error and fatal methods support metadata styling:

// Error with metadata styling
logger.error('Database error', 
  new Error('Connection timeout'),
  { userId: '123', query: 'SELECT * FROM users' },
  { style: 'error', icon: '🔥' }
);

// Fatal error with styling
logger.fatal('Critical system failure', {
  style: { color: ColorEnum.FireRed, bold: true },
  icon: '💀'
});

Icon System

The logger supports a comprehensive set of emoji icons:

// Common icons
logger.info('Success', { icon: '✅' });
logger.warn('Warning', { icon: '⚠️' });
logger.error('Error', { icon: '❌' });
logger.info('Information', { icon: 'ℹ️' });
logger.debug('Debug', { icon: '🐞' });

// Process icons
logger.info('Loading', { icon: '⏳' });
logger.info('Rocket launch', { icon: '🚀' });
logger.info('Fire alert', { icon: '🔥' });
logger.info('Star rating', { icon: '⭐️' });

// Status icons
logger.info('Locked', { icon: '🔒' });
logger.info('Unlocked', { icon: '🔓' });
logger.info('Up arrow', { icon: '⬆️' });
logger.info('Down arrow', { icon: '⬇️' });

// And many more emoji options available...

Fallback and Warning Behavior

The logger gracefully handles unknown styles and icons:

// Unknown semantic style - logs warning and uses default
logger.info('Message', { style: 'unknown-style' });
// Console output: [my-app] Unknown semantic style: unknown-style. Falling back to default.

// Invalid icon - logs warning and uses the string as-is
logger.info('Message', { icon: 'not-an-emoji' });
// Console output: [my-app] Invalid icon: not-an-emoji. Expected a valid emoji or semantic icon name.

Child Logger Inheritance

Child loggers inherit global styling configuration:

const logger = new LoggerService({
  styles: { highlight: { color: ColorEnum.LemonYellow, bold: true } },
  icons: { success: '✅' }
});

const childLogger = logger.forContext('child');
childLogger.info('Child message', { style: 'highlight', icon: 'success' });
// Uses parent's configuration

Color Control

Control color output globally or per-call:

// Disable colors globally
const logger = new LoggerService({
  level: 'info',
  name: 'my-app',
  useColors: false  // Icons still work, colors are disabled
});

// Enable/disable colors at runtime
logger.setUseColors(false);
logger.info('No colors', { style: 'highlight', icon: '🎨' });
// Output: 🎨 [my-app] No colors (no color codes)

logger.setUseColors(true);
logger.info('With colors', { style: 'highlight', icon: '🎨' });
// Output: 🎨 [my-app] With colors (with color codes)

Migration from Legacy Styling

If you were using custom color libraries or formatting, you can now use the built-in metadata system:

// Before (custom color library)
logger.info(chalk.yellow.bold('Important message'));

// After (metadata-based)
logger.info('Important message', { 
  style: { color: ColorEnum.LemonYellow, bold: true } 
});

// Before (manual emoji concatenation)
logger.info('✅ Task completed');

// After (metadata-based)
logger.info('Task completed', { icon: '✅' });

Smart Log Deduplication

The logger includes smart deduplication to reduce noise from repeated log messages. When enabled, identical messages within a time window are batched together and displayed with repeat counts.

Basic Configuration

const logger = new LoggerService({
  level: 'info',
  deduplication: {
    enabled: true,
    windowMs: 5000,        // 5-second batching window (default: 1000ms)
    flushOnCritical: true  // Flush batched messages when error/fatal occurs (default: true)
  }
});

// These repeated messages will be batched
logger.info('Processing file...');
logger.info('Processing file...');
logger.info('Processing file...');

// After 5 seconds, you'll see:
// [my-app] Processing file... (×3)

How It Works

  • Message Batching: Identical messages at the same log level are grouped together within a time window
  • Automatic Flushing: Batched messages are automatically flushed when the time window expires
  • Critical Message Priority: Error and fatal messages always log immediately and optionally flush any pending batches
  • Context Awareness: Messages from different contexts are tracked separately
  • Metadata Bypass: Messages with styling metadata or additional data are never batched (logged immediately)

Key Features

Simple Messages Only

Only simple messages without metadata or additional data are deduplicated:

// These will be batched
logger.info('Connection retry');
logger.info('Connection retry');
logger.info('Connection retry');

// These will NOT be batched (logged immediately)
logger.info('Connection retry', { style: 'warning' });
logger.info('Connection retry', { userId: 123 });
logger.info('Connection retry', additionalData);

Critical Message Handling

Error and fatal messages bypass deduplication entirely:

// Setup with batching
logger.info('Processing...');
logger.info('Processing...');

// This error logs immediately and flushes batched messages
logger.error('Processing failed!');

// Output:
// [my-app] Processing... (×2)
// [my-app] Processing failed!

Level-Specific Batching

Different log levels are batched separately:

logger.debug('Checking status');
logger.info('Checking status');  // Different level, won't batch with debug
logger.debug('Checking status'); // Will batch with first debug message

// Results in separate batches for debug and info levels

Context Separation

Child loggers maintain separate deduplication tracking:

const rootLogger = new LoggerService({ 
  deduplication: { enabled: true } 
});
const userLogger = rootLogger.forContext('user');
const adminLogger = rootLogger.forContext('admin');

// These are tracked separately
rootLogger.info('System status');
userLogger.info('System status');
adminLogger.info('System status');

// Results in three separate log entries

Configuration Options

interface DeduplicationConfig {
  enabled: boolean;         // Enable/disable deduplication
  windowMs?: number;        // Time window in milliseconds (default: 1000)
  flushOnCritical?: boolean; // Flush batches on error/fatal (default: true)
}

Environment Variable Support

You can configure deduplication via environment variables:

# Enable deduplication with 2-second window
LOG_DEDUP_ENABLED=true
LOG_DEDUP_WINDOW=2000
LOG_DEDUP_FLUSH_CRITICAL=true

Best Practices

  • Use for High-Volume Scenarios: Enable deduplication when you expect repeated messages (polling, retry loops, etc.)
  • Keep Short Windows: Use shorter time windows (1-5 seconds) to maintain responsiveness
  • Critical Messages: Always keep flushOnCritical: true to ensure important errors are seen immediately
  • Disable for Development: Consider disabling during development for immediate feedback
  • Monitor Performance: While lightweight, consider the memory footprint in extremely high-volume scenarios

Performance Impact

  • Memory: Minimal overhead - uses in-memory Map with automatic cleanup
  • CPU: Negligible processing overhead per message
  • Timing: Uses Node.js timers with automatic cleanup on flush

Log Grouping

The logger provides methods to visually group related log messages in the console output:

// Start a group
logger.group('Process Name');

// All logs after this will be visually grouped until groupEnd is called
logger.info('First step');
logger.debug('Details about first step');
logger.info('Second step');

// End the group
logger.groupEnd('Process Name');

// Nested groups
logger.group('Parent Process');
logger.info('Parent process started');

logger.group('Child Process');
logger.debug('Child process details');
logger.groupEnd('Child Process');

logger.info('Parent process continued');
logger.groupEnd('Parent Process');

// End a group without specifying name (ends most recent group)
logger.group('Another Group');
logger.info('Some info');
logger.groupEnd(); // Ends 'Another Group'

Groups are particularly useful for:

  • Organizing related log messages (e.g., HTTP request/response cycles)
  • Tracking multi-step processes
  • Debugging complex operations with multiple sub-operations
  • Improving readability of logs with many entries

Enhanced Error Handling

The logger provides robust and flexible error handling with multiple overloads, structured serialization, and full backwards compatibility.

Error Method Overloads

The error() and fatal() methods support multiple call signatures for maximum flexibility:

import { LoggerService } from '@analog-tools/logger';

const logger = new LoggerService({ name: 'my-app' });

// 1. Simple message
logger.error('Something went wrong');

// 2. Error object only
const dbError = new Error('Connection failed');
logger.error(dbError);

// 3. Message with Error object
logger.error('Database operation failed', dbError);

// 4. Message with structured metadata
logger.error('Validation failed', {
  userId: '12345',
  field: 'email',
  value: 'invalid-email',
  timestamp: Date.now()
});

// 5. Message with Error and metadata
logger.error('Payment processing failed', paymentError, {
  orderId: 'order-123',
  amount: 99.99,
  currency: 'USD'
});

// 6. Backwards compatible: message with additional data
logger.error('Operation failed', { context: 'user-service' }, { operation: 'createUser' });

Structured Error Serialization

The logger includes a powerful error serializer that handles complex scenarios:

import { ErrorSerializer } from '@analog-tools/logger';

// Circular reference handling
const obj = { name: 'test' };
obj.self = obj; // Creates circular reference
logger.error('Circular reference detected', obj); // Safely serialized

// Deep object traversal with limits
const deepObj = { level1: { level2: { level3: { level4: 'deep' } } } };
logger.error('Deep object logging', deepObj); // Respects max depth

// Custom error serialization
const customError = ErrorSerializer.serialize(error, {
  includeStack: false,          // Exclude stack traces
  maxDepth: 5,                 // Limit object depth
  includeNonEnumerable: true   // Include hidden properties
});

LogContext Interface

Use the structured LogContext interface for consistent logging:

import { LogContext } from '@analog-tools/logger';

const context: LogContext = {
  correlationId: 'req-123',
  userId: 'user-456',
  requestId: 'api-789',
  context: {
    service: 'auth',
    operation: 'login',
    duration: 150
  }
};

logger.error('Authentication failed', authError, context);

Error Handling Patterns

Service Layer Error Handling

class UserService {
  static INJECTABLE = true;
  private logger = inject(LoggerService).forContext('UserService');

  async createUser(userData: CreateUserRequest): Promise<User> {
    const context: LogContext = {
      operation: 'createUser',
      correlationId: userData.correlationId
    };

    try {
      const user = await this.userRepository.create(userData);
      this.logger.info('User created successfully', { 
        ...context, 
        userId: user.id 
      });
      return user;
    } catch (error) {
      // Enhanced error logging with Error object and context
      this.logger.error('User creation failed', error, {
        ...context,
        email: userData.email,
        provider: userData.provider
      });
      throw error;
    }
  }
}

API Route Error Handling

// src/server/routes/api/users/[id].ts
import { defineEventHandler, getRouterParam, createError } from 'h3';
import { withLogging, LogMetadata } from '@analog-tools/logger';

export default withLogging(
  defineEventHandler(async (event) => {
    const logger = inject(LoggerService).forContext('users-api');
    const userId = getRouterParam(event, 'id');

    const context: LogContext = {
      requestId: event.context.requestId,
      method: event.node.req.method,
      path: event.node.req.url,
      userId
    };

    try {
      if (!userId) {
        logger.warn('User ID missing from request', context);
        throw createError({
          statusCode: 400,
          statusMessage: 'User ID is required'
        });
      }

      const user = await getUserById(userId);
      logger.info('User API request successful', context);
      return user;
    } catch (error) {
      // Log with Error object and structured context
      logger.error('User API request failed', error, context);
      throw error;
    }
  }),
  { namespace: 'users-api', level: 'info' }
);

Performance Considerations

Optimize your logging for production environments:

Log Level Configuration

// Development
const devLogger = new LoggerService({ 
  level: 'debug', 
  name: 'my-app-dev' 
});

// Production
const prodLogger = new LoggerService({ 
  level: 'info',  // Avoid trace/debug in production
  name: 'my-app-prod',
  disabledContexts: ['verbose-module', 'debug-info']
});

Efficient Context Usage

// Good: Create context loggers once and reuse
class DatabaseService {
  private logger = inject(LoggerService).forContext('database');
  
  async query(sql: string) {
    this.logger.debug('Executing query', { sql });
    // Implementation...
  }
}

// Avoid: Creating new context loggers repeatedly
async function badExample() {
  for (let i = 0; i < 1000; i++) {
    const logger = inject(LoggerService).forContext('loop'); // Inefficient
    logger.debug(`Iteration ${i}`);
  }
}

Environment-Based Configuration

// Use environment variables for production tuning
const logger = new LoggerService({
  level: process.env['LOG_LEVEL'] || 'info',
  name: process.env['APP_NAME'] || 'my-app',
  disabledContexts: process.env['LOG_DISABLED_CONTEXTS']?.split(',') || []
});

Troubleshooting

Common Issues

Missing @analog-tools/inject dependency

Problem: TypeScript errors or runtime errors related to inject() function. Solution: Install the peer dependency or use standalone approach:

# Option 1: Install the peer dependency
npm install @analog-tools/inject

# Option 2: Remove injection usage and use standalone
# Replace inject(LoggerService) with new LoggerService()

Colors not showing in output

Problem: Log messages appear without colors in development. Solution: Colors are automatically disabled in test environments. To force enable:

const logger = new LoggerService({ useColors: true });
// Or check if you're in a test environment
const isTest = process.env['NODE_ENV'] === 'test' || process.env['VITEST'] === 'true';
const logger = new LoggerService({ useColors: !isTest });

Context logging not working

Problem: Child logger contexts don't appear in output. Solution: Check if the context is in your disabled contexts list:

// Clear disabled contexts
logger.setDisabledContexts([]);

// Or check current disabled contexts
console.log(logger.getDisabledContexts());

// Remove specific context from disabled list
const current = logger.getDisabledContexts();
const filtered = current.filter(ctx => ctx !== 'my-context');
logger.setDisabledContexts(filtered);

Log levels not working as expected

Problem: Debug messages appear in production or info messages don't show. Solution: Check your log level configuration:

// Check current log level
console.log('Current log level:', logger.getLogLevel());

// Set log level explicitly
const logger = new LoggerService({ level: 'info' });

// Or use environment variable
// LOG_LEVEL=debug npm start

Performance Issues

High memory usage with extensive logging

Problem: Application memory usage grows with heavy logging. Solutions:

  • Use appropriate log levels for production (info and above)
  • Disable verbose contexts: LOG_DISABLED_CONTEXTS=debug-module,verbose-api
  • Avoid logging large objects frequently
  • Use structured logging instead of string concatenation

Slow application startup

Problem: Application takes long to start with logger configuration. Solution: Optimize logger initialization:

// Lazy initialization
let _logger: LoggerService;
function getLogger() {
  if (!_logger) {
    _logger = new LoggerService({ level: 'info', name: 'my-app' });
  }
  return _logger;
}

Best Practices

Logging Best Practices

  • Use structured logging: Pass structured data as additional parameters instead of string concatenation
  • Create context-specific loggers: Use forContext() to create loggers for different parts of your application
  • Configure log levels appropriately: Use trace and debug for development, info and above for production
  • Log meaningful errors: Include the actual Error object in error logs, not just messages
  • Disable noisy contexts: Use disabledContexts to selectively disable logging for specific contexts
  • Use dependency injection: Leverage @analog-tools/inject for cleaner code organization
  • Use Nitro integration: Use the provided middleware and handlers in AnalogJS API routes
  • Configure via environment: Use environment variables for production configuration

Enhanced Error Handling Best Practices

  • Use appropriate error overloads: Choose the right method signature for your use case:

    // ✅ For simple errors
    logger.error('Operation failed');
    
    // ✅ For errors with Error objects
    logger.error('Database error', dbError);
    
    // ✅ For structured metadata
    logger.error('Validation failed', { field: 'email', value: 'invalid' });
    
    // ✅ For comprehensive error logging
    logger.error('Payment failed', paymentError, { orderId: '123', amount: 99.99 });
    
  • Use LogContext interface: Structure your metadata consistently:

    const context: LogContext = {
      correlationId: 'req-123',
      userId: 'user-456',
      context: { service: 'payments', operation: 'charge' }
    };
    logger.error('Payment processing failed', paymentError, context);
    
  • Handle circular references: The logger automatically handles circular references, but be mindful of object complexity:

    // ✅ Safe - circular references are detected and handled
    const user = { id: '123', profile: {} };
    user.profile.user = user;
    logger.error('User error', user);
    
  • Configure serialization options: Customize error serialization when needed:

    import { ErrorSerializer } from '@analog-tools/logger';
    
    // For sensitive environments - exclude stack traces
    const serialized = ErrorSerializer.serialize(error, {
      includeStack: false,
      maxDepth: 3,
      includeNonEnumerable: true
    });
    

Security Considerations

Avoiding Sensitive Data in Logs

Never log sensitive information such as passwords, API keys, or personal data:

// ❌ DON'T: Log sensitive data
logger.info('User login attempt', {
  username: 'john.doe',
  password: 'secret123', // Never log passwords!
  apiKey: 'sk-1234567890' // Never log API keys!
});

// ✅ DO: Log safely with sanitized data
logger.info('User login attempt', {
  username: 'john.doe',
  hasPassword: true, // Boolean indicator instead
  apiKeyPrefix: 'sk-***', // Partial information only
  timestamp: new Date().toISOString()
});

Sanitizing User Input

Always sanitize user input before logging:

import { LoggerService } from '@analog-tools/logger';

function sanitizeForLogging(input: unknown): unknown {
  if (typeof input === 'string') {
    // Remove potential sensitive patterns
    return input
      .replace(/password[=:]\s*\S+/gi, 'password=***')
      .replace(/token[=:]\s*\S+/gi, 'token=***')
      .replace(/key[=:]\s*\S+/gi, 'key=***');
  }
  return input;
}

// Usage in your application
const logger = new LoggerService({ name: 'api' });

function handleUserInput(userInput: string) {
  logger.info('Processing user input', {
    input: sanitizeForLogging(userInput),
    length: userInput.length
  });
}

Production Log Security

// Configure different log levels for different environments
const logger = new LoggerService({
  level: process.env['NODE_ENV'] === 'production' ? 'warn' : 'debug',
  name: 'secure-app',
  // Disable detailed contexts in production
  disabledContexts: process.env['NODE_ENV'] === 'production' 
    ? ['debug-details', 'user-data', 'internal'] 
    : []
});

Compliance Considerations

For applications handling sensitive data (GDPR, HIPAA, etc.):

// Create a compliance-aware logger
class ComplianceLogger extends LoggerService {
  static INJECTABLE = true;

  // Override methods to add compliance checks
  info(message: string, ...data: unknown[]): void {
    const sanitizedData = data.map(item => this.sanitizeCompliance(item));
    super.info(message, ...sanitizedData);
  }

  private sanitizeCompliance(data: unknown): unknown {
    if (typeof data === 'object' && data !== null) {
      const sanitized = { ...data as Record<string, unknown> };
      
      // Remove common PII fields
      delete sanitized.ssn;
      delete sanitized.creditCard;
      delete sanitized.email; // or hash it
      delete sanitized.phoneNumber;
      
      return sanitized;
    }
    return data;
  }
}

Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

  • Clone the repository
  • Install dependencies: npm install
  • Run tests: nx test
  • Serve Demo App: nx serve

Reporting Issues

  • Use GitHub Issues for bug reports and feature requests
  • Include code examples and error messages
  • Specify your environment (Node.js version, AnalogJS version, etc.)

Support Channels

Future Plans

  • Support for custom transports (file, HTTP, etc.)
  • Support for log rotation and compression
  • Structured JSON logging format option
  • Performance optimizations for high-volume logging

License

This project is licensed under the MIT License - see the LICENSE file for details.

Metadata-Based Styling and Icon System

@analog-tools/logger now supports a powerful metadata-based styling and icon system for all log methods. This enables semantic and custom styles, emoji icons, and per-call or global configuration for beautiful, expressive logs.

Usage Example

import { LoggerService, ColorEnum } from '@analog-tools/logger';

const logger = new LoggerService({
  level: 'info',
  name: 'my-app',
  // Optional: global style/icon config
  styles: { highlight: { color: ColorEnum.LemonYellow, bold: true } },
  icons: { success: '✅', info: 'ℹ️' }
});

// Per-call metadata for style and icon
logger.info('Success!', { style: 'success', icon: '✅' });
logger.warn('Be careful', { icon: '⚠️' });
logger.info('Custom dreamy color', { style: { color: ColorEnum.DeepPurple, underline: true } });
logger.info('With emoji', { icon: '🚀' });

Supported Features

  • Semantic styles: Use names like 'success', 'warning', 'highlight', etc.
  • Custom styles: Use ColorEnum and style config for color, bold, underline, background, etc.
  • Emoji icons: Use any emoji or semantic icon name (e.g., 'success', 'info').
  • Global config: Set default styles/icons in LoggerConfig.
  • Per-call override: Pass metadata as the last argument to any log method.
  • Fallback/warning: Unknown styles/icons trigger a warning and fallback to defaults.

Example: Highlighted Info (replaces info2())

// Old:
// logger.info2('Important!');
// New:
logger.info('Important!', { style: 'highlight', icon: '⭐️' });

See the Migration Guide for upgrade instructions and more examples.

Keywords

analogjs

FAQs

Package last updated on 21 Aug 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts