
Research
/Security News
Weaponizing Discord for Command and Control Across npm, PyPI, and RubyGems.org
Socket researchers uncover how threat actors weaponize Discord across the npm, PyPI, and RubyGems ecosystems to exfiltrate sensitive data.
@analog-tools/logger
Advanced tools
β οΈ 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.
LogLevel
union type: trace
, debug
, info
, warn
, error
, fatal
, silent
@analog-tools/logger | AnalogJS | Node.js | TypeScript |
---|---|---|---|
0.0.5 | β₯ 1.0.0 | β₯ 18.13 | β₯ 4.8 |
# Using npm
npm install @analog-tools/logger
# Using pnpm
pnpm add @analog-tools/logger
# Using yarn
yarn add @analog-tools/logger
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: 'ποΈ' });
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');
@analog-tools/logger provides comprehensive type safety through TypeScript, ensuring compile-time validation and excellent developer experience.
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'
};
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"...
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
}
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
});
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'
};
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.
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');
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...
}
}
Use Standalone When:
Use with @analog-tools/inject When:
You can easily switch between standalone and injection patterns:
inject(LoggerService)
with new LoggerService()
inject(LoggerService)
// 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' };
});
// 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' };
});
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' };
});
// src/server/middleware/logging.ts
import { createLoggerMiddleware } from '@analog-tools/logger';
export default createLoggerMiddleware('api-requests');
// 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)
}
);
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'
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
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;
}
// 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>;
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;
}
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
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' })
);
});
});
@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.
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: 'π' });
'success'
, 'warning'
, 'highlight'
, etc.ColorEnum
and style config for color, bold, underline, background, etc.'success'
, 'info'
).LoggerConfig
.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' });
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
// 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: 'β‘οΈ' }
);
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: 'π'
});
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...
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 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
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)
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: 'β
' });
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.
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)
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);
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!
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
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
interface DeduplicationConfig {
enabled: boolean; // Enable/disable deduplication
windowMs?: number; // Time window in milliseconds (default: 1000)
flushOnCritical?: boolean; // Flush batches on error/fatal (default: true)
}
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
flushOnCritical: true
to ensure important errors are seen immediatelyThe 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:
The logger provides robust and flexible error handling with multiple overloads, structured serialization, and full backwards compatibility.
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' });
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
});
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);
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;
}
}
}
// 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' }
);
Optimize your logging for production environments:
// 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']
});
// 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}`);
}
}
// 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(',') || []
});
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()
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 });
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);
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
Problem: Application memory usage grows with heavy logging. Solutions:
info
and above)LOG_DISABLED_CONTEXTS=debug-module,verbose-api
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;
}
forContext()
to create loggers for different parts of your applicationtrace
and debug
for development, info
and above for productiondisabledContexts
to selectively disable logging for specific contextsUse 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
});
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()
});
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
});
}
// 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']
: []
});
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;
}
}
We welcome contributions! Please see our Contributing Guide for details.
npm install
nx test
nx serve
This project is licensed under the MIT License - see the LICENSE file for details.
@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.
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: 'π' });
'success'
, 'warning'
, 'highlight'
, etc.ColorEnum
and style config for color, bold, underline, background, etc.'success'
, 'info'
).LoggerConfig
.info2()
)// Old:
// logger.info2('Important!');
// New:
logger.info('Important!', { style: 'highlight', icon: 'βοΈ' });
See the Migration Guide for upgrade instructions and more examples.
FAQs
Logging utility for AnalogJS applications
The npm package @analog-tools/logger receives a total of 55 weekly downloads. As such, @analog-tools/logger popularity was classified as not popular.
We found that @analog-tools/logger demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?
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.
Research
/Security News
Socket researchers uncover how threat actors weaponize Discord across the npm, PyPI, and RubyGems ecosystems to exfiltrate sensitive data.
Security News
Socket now integrates with Bun 1.3βs Security Scanner API to block risky packages at install time and enforce your organizationβs policies in local dev and CI.
Research
The Socket Threat Research Team is tracking weekly intrusions into the npm registry that follow a repeatable adversarial playbook used by North Korean state-sponsored actors.