@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.

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
Installation
npm install @analog-tools/logger
pnpm add @analog-tools/logger
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';
const logger = new LoggerService({ level: 'info', name: 'my-app' });
logger.info('Hello from @analog-tools/logger!');
logger.warn('This is a warning message');
logger.error('Error occurred', new Error('Something went wrong'));
logger.info('Success!', { style: 'success', icon: '✅' });
logger.warn('Be careful', { icon: '⚠️' });
logger.error('Critical error', {
style: { color: ColorEnum.FireRed, bold: true },
icon: '🔥'
});
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';
const logger = new LoggerService({
level: 'info',
name: 'my-app',
});
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'));
const authLogger = logger.forContext('auth');
authLogger.info('User authenticated');
logger.group('API Request');
logger.info('Processing request to /users');
logger.debug('Validating request body');
logger.info('Request processed successfully');
logger.groupEnd('API Request');
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';
const validLevels: LogLevel[] = [
'trace', 'debug', 'info', 'warn', 'error', 'fatal', 'silent'
];
const config: LoggerConfig = {
level: 'debug',
name: 'my-app'
};
const invalidConfig: LoggerConfig = {
level: 'verbose',
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';
if (isValidLogLevel(externalLevel)) {
const logger = new LoggerService({ level: externalLevel });
} else {
console.warn(`Invalid log level: ${externalLevel}`);
}
const logger = new LoggerService({
level: 'INVALID' as LogLevel,
name: 'my-app'
});
LogLevel Enum
For scenarios where you need numeric log level values:
import { LogLevelEnum } from '@analog-tools/logger';
console.log(LogLevelEnum.trace);
console.log(LogLevelEnum.debug);
console.log(LogLevelEnum.info);
console.log(LogLevelEnum.warn);
console.log(LogLevelEnum.error);
console.log(LogLevelEnum.fatal);
console.log(LogLevelEnum.silent);
if (currentLogLevel >= LogLevelEnum.warn) {
}
Type-Safe Nitro Integration
The Nitro integration also supports type-safe log levels:
import { withLogging, LogLevel } from '@analog-tools/logger';
export default withLogging(myHandler, {
namespace: 'api',
level: 'debug' as LogLevel,
logResponse: true
});
export default withLogging(myHandler, {
level: 'verbose'
});
Migration from String Types
If you're migrating from a version that used generic string
types:
interface OldConfig {
level?: string;
}
interface NewConfig {
level?: LogLevel;
}
const config: LoggerConfig = {
level: process.env.LOG_LEVEL as LogLevel,
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';
const logger = new LoggerService({
level: 'debug',
name: 'my-app',
disabledContexts: ['verbose-module']
});
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 });
}
}
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;
}
}
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';
registerService(LoggerService, {
level: 'debug',
name: 'my-app',
disabledContexts: ['verbose-module']
});
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 });
}
}
class ProductService {
private logger = inject(LoggerService).forContext('products');
getProduct(id: string) {
this.logger.info(`Fetching product ${id}`);
}
}
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)
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,
});
return { id: userId, name: 'Example User' };
});
Basic Usage (Standalone)
import { defineEventHandler, getRouterParam } from 'h3';
import { LoggerService } from '@analog-tools/logger';
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,
});
return { id: userId, name: 'Example User' };
});
Shared Logger Configuration for API Routes
For standalone usage, create a shared logger module:
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 const usersApiLogger = apiLogger.forContext('users');
export const productsApiLogger = apiLogger.forContext('products');
export const authApiLogger = apiLogger.forContext('auth');
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,
});
return { id: userId, name: 'Example User' };
});
With Nitro Middleware
import { createLoggerMiddleware } from '@analog-tools/logger';
export default createLoggerMiddleware('api-requests');
With Request Handler Wrapper
import { defineEventHandler } from 'h3';
import { withLogging } from '@analog-tools/logger';
export default withLogging(
defineEventHandler(() => {
return { products: [] };
}),
{
namespace: 'products-api',
level: 'info',
logResponse: true
}
);
Context-Based Logging
Create child loggers for specific contexts:
const logger = new LoggerService();
const authLogger = logger.forContext('auth');
const dbLogger = logger.forContext('database');
authLogger.info('User logged in');
dbLogger.error('Database connection failed');
Disabling Specific Contexts
You can disable logging for specific contexts:
const logger = new LoggerService({
disabledContexts: ['verbose-module', 'debug-info']
});
logger.setDisabledContexts(['verbose-module', 'debug-info']);
API Reference
LoggerService
The main logger class:
class LoggerService implements ILogger {
static INJECTABLE = true;
constructor(config?: LoggerConfig);
forContext(context: string): ILogger;
getLogLevel(): LogLevel;
getDisabledContexts(): string[];
setDisabledContexts(contexts: string[]): void;
setUseColors(enabled: boolean): void;
getUseColors(): boolean;
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 {
level?: string;
name?: string;
disabledContexts?: string[];
useColors?: boolean;
}
Nitro Integration
createLoggerMiddleware(namespace: string = 'api'): EventHandler;
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;
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);
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',
styles: { highlight: { color: ColorEnum.LemonYellow, bold: true } },
icons: { success: '✅', info: 'ℹ️' }
});
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()
)
logger.info('Important!', { style: 'highlight', icon: '⭐️' });
See the Migration Guide for upgrade instructions and more examples.
Use predefined semantic styles for common scenarios:
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: '🐞'
}
});
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:
ColorEnum.SkyBlue
ColorEnum.OceanBlue
ColorEnum.MidnightBlue
ColorEnum.MintGreen
ColorEnum.ForestGreen
ColorEnum.EmeraldGreen
ColorEnum.LemonYellow
ColorEnum.SunflowerYellow
ColorEnum.GoldYellow
ColorEnum.RoseRed
ColorEnum.FireRed
ColorEnum.BurgundyRed
ColorEnum.LavenderPurple
ColorEnum.RoyalPurple
ColorEnum.DeepPurple
ColorEnum.PeachOrange
ColorEnum.TangerineOrange
ColorEnum.AmberOrange
ColorEnum.SilverGray
ColorEnum.SlateGray
ColorEnum.CharcoalGray
ColorEnum.SkyBlueBg
ColorEnum.ForestGreenBg
Advanced Styling Options
logger.info('Formatted message', {
style: {
color: ColorEnum.RoyalPurple,
bold: true,
underline: true
},
icon: '🎨'
});
logger.info('User logged in',
{ userId: '123', timestamp: Date.now() },
{ style: 'success', icon: '👤' }
);
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:
logger.error('Database error',
new Error('Connection timeout'),
{ userId: '123', query: 'SELECT * FROM users' },
{ style: 'error', icon: '🔥' }
);
logger.fatal('Critical system failure', {
style: { color: ColorEnum.FireRed, bold: true },
icon: '💀'
});
Icon System
The logger supports a comprehensive set of emoji icons:
logger.info('Success', { icon: '✅' });
logger.warn('Warning', { icon: '⚠️' });
logger.error('Error', { icon: '❌' });
logger.info('Information', { icon: 'ℹ️' });
logger.debug('Debug', { icon: '🐞' });
logger.info('Loading', { icon: '⏳' });
logger.info('Rocket launch', { icon: '🚀' });
logger.info('Fire alert', { icon: '🔥' });
logger.info('Star rating', { icon: '⭐️' });
logger.info('Locked', { icon: '🔒' });
logger.info('Unlocked', { icon: '🔓' });
logger.info('Up arrow', { icon: '⬆️' });
logger.info('Down arrow', { icon: '⬇️' });
Fallback and Warning Behavior
The logger gracefully handles unknown styles and icons:
logger.info('Message', { style: 'unknown-style' });
logger.info('Message', { icon: 'not-an-emoji' });
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' });
Color Control
Control color output globally or per-call:
const logger = new LoggerService({
level: 'info',
name: 'my-app',
useColors: false
});
logger.setUseColors(false);
logger.info('No colors', { style: 'highlight', icon: '🎨' });
logger.setUseColors(true);
logger.info('With colors', { style: 'highlight', icon: '🎨' });
Migration from Legacy Styling
If you were using custom color libraries or formatting, you can now use the built-in metadata system:
logger.info(chalk.yellow.bold('Important message'));
logger.info('Important message', {
style: { color: ColorEnum.LemonYellow, bold: true }
});
logger.info('✅ Task completed');
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,
flushOnCritical: true
}
});
logger.info('Processing file...');
logger.info('Processing file...');
logger.info('Processing file...');
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:
logger.info('Connection retry');
logger.info('Connection retry');
logger.info('Connection retry');
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:
logger.info('Processing...');
logger.info('Processing...');
logger.error('Processing failed!');
Level-Specific Batching
Different log levels are batched separately:
logger.debug('Checking status');
logger.info('Checking status');
logger.debug('Checking status');
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');
rootLogger.info('System status');
userLogger.info('System status');
adminLogger.info('System status');
Configuration Options
interface DeduplicationConfig {
enabled: boolean;
windowMs?: number;
flushOnCritical?: boolean;
}
Environment Variable Support
You can configure deduplication via environment variables:
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:
logger.group('Process Name');
logger.info('First step');
logger.debug('Details about first step');
logger.info('Second step');
logger.groupEnd('Process Name');
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');
logger.group('Another Group');
logger.info('Some info');
logger.groupEnd();
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' });
logger.error('Something went wrong');
const dbError = new Error('Connection failed');
logger.error(dbError);
logger.error('Database operation failed', dbError);
logger.error('Validation failed', {
userId: '12345',
field: 'email',
value: 'invalid-email',
timestamp: Date.now()
});
logger.error('Payment processing failed', paymentError, {
orderId: 'order-123',
amount: 99.99,
currency: 'USD'
});
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';
const obj = { name: 'test' };
obj.self = obj;
logger.error('Circular reference detected', obj);
const deepObj = { level1: { level2: { level3: { level4: 'deep' } } } };
logger.error('Deep object logging', deepObj);
const customError = ErrorSerializer.serialize(error, {
includeStack: false,
maxDepth: 5,
includeNonEnumerable: true
});
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) {
this.logger.error('User creation failed', error, {
...context,
email: userData.email,
provider: userData.provider
});
throw error;
}
}
}
API Route Error Handling
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) {
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
const devLogger = new LoggerService({
level: 'debug',
name: 'my-app-dev'
});
const prodLogger = new LoggerService({
level: 'info',
name: 'my-app-prod',
disabledContexts: ['verbose-module', 'debug-info']
});
Efficient Context Usage
class DatabaseService {
private logger = inject(LoggerService).forContext('database');
async query(sql: string) {
this.logger.debug('Executing query', { sql });
}
}
async function badExample() {
for (let i = 0; i < 1000; i++) {
const logger = inject(LoggerService).forContext('loop');
logger.debug(`Iteration ${i}`);
}
}
Environment-Based Configuration
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:
npm install @analog-tools/inject
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 });
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:
logger.setDisabledContexts([]);
console.log(logger.getDisabledContexts());
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:
console.log('Current log level:', logger.getLogLevel());
const logger = new LoggerService({ level: 'info' });
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:
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:
logger.error('Operation failed');
logger.error('Database error', dbError);
logger.error('Validation failed', { field: 'email', value: 'invalid' });
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:
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';
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:
logger.info('User login attempt', {
username: 'john.doe',
password: 'secret123',
apiKey: 'sk-1234567890'
});
logger.info('User login attempt', {
username: 'john.doe',
hasPassword: true,
apiKeyPrefix: 'sk-***',
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') {
return input
.replace(/password[=:]\s*\S+/gi, 'password=***')
.replace(/token[=:]\s*\S+/gi, 'token=***')
.replace(/key[=:]\s*\S+/gi, 'key=***');
}
return input;
}
const logger = new LoggerService({ name: 'api' });
function handleUserInput(userInput: string) {
logger.info('Processing user input', {
input: sanitizeForLogging(userInput),
length: userInput.length
});
}
Production Log Security
const logger = new LoggerService({
level: process.env['NODE_ENV'] === 'production' ? 'warn' : 'debug',
name: 'secure-app',
disabledContexts: process.env['NODE_ENV'] === 'production'
? ['debug-details', 'user-data', 'internal']
: []
});
Compliance Considerations
For applications handling sensitive data (GDPR, HIPAA, etc.):
class ComplianceLogger extends LoggerService {
static INJECTABLE = true;
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> };
delete sanitized.ssn;
delete sanitized.creditCard;
delete sanitized.email;
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',
styles: { highlight: { color: ColorEnum.LemonYellow, bold: true } },
icons: { success: '✅', info: 'ℹ️' }
});
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()
)
logger.info('Important!', { style: 'highlight', icon: '⭐️' });
See the Migration Guide for upgrade instructions and more examples.