@voilajsx/appkit
Version:
Minimal and framework agnostic Node.js toolkit designed for AI agentic backend development
843 lines • 35.9 kB
JavaScript
/**
* Core logger class with visual error display and simplified transport management
* @module @voilajsx/appkit/logger
* @file src/logger/logger.ts
*
* @llm-rule WHEN: Building logger instances - called via loggerClass.get(), not directly
* @llm-rule AVOID: Creating LoggerClass directly - always use loggerClass.get() for proper setup
* @llm-rule NOTE: Enhanced error() method provides automatic visual formatting for better DX
*/
import { ConsoleTransport } from './transports/console.js';
import { FileTransport } from './transports/file.js';
import { DatabaseTransport } from './transports/database.js';
import { HttpTransport } from './transports/http.js';
import { WebhookTransport } from './transports/webhook.js';
import { existsSync } from 'fs';
import { join } from 'path';
const LOG_LEVELS = {
error: 0,
warn: 1,
info: 2,
debug: 3,
};
/**
* Logger class with automatic transport management and enhanced error() method
*/
export class LoggerClass {
level;
levelValue;
defaultMeta;
config;
transports = new Map();
pendingWrites = [];
constructor(config) {
this.config = config;
this.level = config.level;
this.levelValue = LOG_LEVELS[this.level];
this.defaultMeta = {
service: config.service.name,
version: config.service.version,
environment: config.service.environment,
};
this.initializeTransports();
}
initializeTransports() {
const { transports } = this.config;
if (transports.console) {
this.transports.set('console', new ConsoleTransport(this.config));
}
if (transports.file) {
try {
this.transports.set('file', new FileTransport(this.config));
}
catch (error) {
console.error('File transport initialization failed:', error.message);
}
}
if (transports.database && this.config.database.url) {
try {
this.transports.set('database', new DatabaseTransport(this.config));
}
catch (error) {
console.error('Database transport initialization failed:', error.message);
}
}
if (transports.http && this.config.http.url) {
try {
this.transports.set('http', new HttpTransport(this.config));
}
catch (error) {
console.error('HTTP transport initialization failed:', error.message);
}
}
if (transports.webhook && this.config.webhook.url) {
try {
this.transports.set('webhook', new WebhookTransport(this.config));
}
catch (error) {
console.error('Webhook transport initialization failed:', error.message);
}
}
if (this.transports.size === 0) {
console.warn('No transports initialized, falling back to console');
this.transports.set('console', new ConsoleTransport(this.config));
}
}
/**
* Log informational message
* @llm-rule WHEN: Normal application events, user actions, business logic flow
* @llm-rule AVOID: Sensitive data in meta - passwords, tokens, full card numbers
*/
info(message, meta = {}) {
this.log('info', message, meta);
}
/**
* Enhanced error logging with automatic visual formatting
* @llm-rule WHEN: Exceptions, failures, critical issues requiring attention
* @llm-rule AVOID: Using for warnings - errors should indicate actual problems
* @llm-rule NOTE: Automatically provides visual formatting in development with smart diagnostics
*/
error(message, meta = {}) {
const enhancedMeta = {
...meta,
_location: this.getCaller()
};
// Detect error type and provide visual formatting if appropriate
if (this.shouldShowVisual()) {
this.renderVisualError(message, enhancedMeta);
}
// Always log structured data for production/debugging
this.log('error', message, enhancedMeta);
}
/**
* Log warning message
* @llm-rule WHEN: Potential issues, deprecated usage, performance concerns
* @llm-rule AVOID: Using for normal recoverable errors - use error() for those
*/
warn(message, meta = {}) {
this.log('warn', message, meta);
}
/**
* Log debug message
* @llm-rule WHEN: Development debugging, detailed flow information
* @llm-rule AVOID: Production debug spam - automatically filtered in production
*/
debug(message, meta = {}) {
this.log('debug', message, meta);
}
/**
* Create child logger with additional context
* @llm-rule WHEN: Adding component context or request-specific data
* @llm-rule AVOID: Creating many child loggers - reuse component loggers
*/
child(bindings) {
const child = Object.create(this);
child.defaultMeta = { ...this.defaultMeta, ...bindings };
return child;
}
// ============================================================================
// VISUAL ERROR FORMATTING METHODS
// ============================================================================
shouldShowVisual() {
// Show visual output in development or when explicitly enabled
return this.config.service.environment === 'development' ||
this.config.minimal === false ||
process.env.VOILA_VISUAL_ERRORS === 'true';
}
renderVisualError(message, meta) {
const errorType = this.detectErrorType(message, meta);
const title = this.getErrorTitle(errorType);
const diagnostics = this.generateDiagnostics(message, meta, errorType);
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
gray: '\x1b[90m'
};
console.log();
console.log(`${colors.red}╭─ ${title}${colors.reset}`);
if (meta.feature) {
console.log(`${colors.red}│${colors.reset} Feature: ${colors.cyan}${meta.feature}${colors.reset}`);
}
if (meta.file) {
console.log(`${colors.red}│${colors.reset} File: ${colors.yellow}${meta.file}${colors.reset}`);
}
console.log(`${colors.red}│${colors.reset}`);
// ✅ Enhanced error message with wrapping
const errorPrefix = `${colors.red}❌ ERROR:${colors.reset}`;
const wrappedMessage = this.wrapErrorMessage(message, errorPrefix);
wrappedMessage.forEach((line, index) => {
if (index === 0) {
console.log(`${colors.red}│${colors.reset} ${line}`);
}
else {
console.log(`${colors.red}│${colors.reset}${line}`); // Continuation lines
}
});
// Show location/position if available
if (meta._location) {
console.log(`${colors.red}│${colors.reset} ${colors.cyan}📍 FROM:${colors.reset} ${meta._location}`);
}
// Extract and show line/position for syntax errors with wrapping
const lineMatch = message.match(/at line (\d+):(\d+)/i) || message.match(/line (\d+)/i);
if (lineMatch) {
console.log(`${colors.red}│${colors.reset} ${colors.cyan}📍 LINE:${colors.reset} ${lineMatch[1]}${lineMatch[2] ? `:${lineMatch[2]}` : ''}`);
}
// Show code context for syntax errors if available
if (message.includes('Unexpected token') || message.includes('SyntaxError')) {
const tokenMatch = message.match(/'([^']+)'/);
if (tokenMatch) {
console.log(`${colors.red}│${colors.reset} ${colors.cyan}📍 NEAR:${colors.reset} ...${tokenMatch[1]}...`);
console.log(`${colors.red}│${colors.reset} ${colors.red}^${colors.reset}`);
}
}
console.log(`${colors.red}│${colors.reset}`);
// Show smart diagnostics
diagnostics.forEach(diagnostic => {
const icon = diagnostic.type === 'error' ? '✗' :
diagnostic.type === 'warning' ? '⚠' :
diagnostic.type === 'success' ? '✓' : 'ℹ';
const color = diagnostic.type === 'error' ? colors.red :
diagnostic.type === 'warning' ? colors.yellow :
diagnostic.type === 'success' ? colors.green : colors.cyan;
// Wrap diagnostic messages too
const diagnosticPrefix = `${color}${icon}${colors.reset}`;
const wrappedDiagnostic = this.wrapErrorMessage(diagnostic.message, diagnosticPrefix);
wrappedDiagnostic.forEach((line, index) => {
if (index === 0) {
console.log(`${colors.red}│${colors.reset} ${line}`);
}
else {
console.log(`${colors.red}│${colors.reset}${line}`);
}
});
if (diagnostic.fix) {
const fixPrefix = `${colors.green}✓${colors.reset}`;
const wrappedFix = this.wrapErrorMessage(diagnostic.fix, fixPrefix);
wrappedFix.forEach((line, index) => {
if (index === 0) {
console.log(`${colors.red}│${colors.reset} ${line}`);
}
else {
console.log(`${colors.red}│${colors.reset}${line}`);
}
});
}
});
const solutions = this.getSolutions(errorType, message);
if (solutions.length > 0) {
console.log(`${colors.red}│${colors.reset}`);
solutions.forEach(solution => {
const solutionPrefix = `${colors.green}✓${colors.reset}`;
const wrappedSolution = this.wrapErrorMessage(solution, solutionPrefix);
wrappedSolution.forEach((line, index) => {
if (index === 0) {
console.log(`${colors.red}│${colors.reset} ${line}`);
}
else {
console.log(`${colors.red}│${colors.reset}${line}`);
}
});
});
}
// Enhanced fix message based on error type
const fixMessage = this.getFixMessage(errorType, message);
console.log(`${colors.red}╰─ FIX: ${colors.green}${fixMessage}${colors.reset}`);
console.log();
}
/**
* Wrap long error messages with proper indentation and path handling
* @param message - The message to wrap
* @param prefix - The prefix (like "❌ ERROR:" or "✗") to account for in width
*/
wrapErrorMessage(message, prefix = '') {
const maxWidth = 85; // Terminal width accounting for borders
const indent = ' '; // 9 spaces for continuation lines
const prefixLength = this.stripAnsiCodes(prefix).length; // Account for prefix in first line
const firstLineMaxWidth = maxWidth - prefixLength - 1; // -1 for space after prefix
const continuationMaxWidth = maxWidth - indent.length;
const lines = [];
let currentLine = '';
let isFirstLine = true;
// Handle very long paths/URLs by splitting them intelligently
const processedMessage = this.preprocessLongPaths(message);
const words = processedMessage.split(' ');
for (const word of words) {
const currentMaxWidth = isFirstLine ? firstLineMaxWidth : continuationMaxWidth;
const proposedLine = currentLine + (currentLine ? ' ' : '') + word;
if (proposedLine.length > currentMaxWidth) {
// Current line would be too long
if (currentLine) {
// Save current line
if (isFirstLine) {
lines.push(`${prefix}${currentLine ? ' ' + currentLine : ''}`);
isFirstLine = false;
}
else {
lines.push(`${indent}${currentLine}`);
}
currentLine = word;
}
else {
// Single word is too long, force it on its own line
if (isFirstLine) {
lines.push(`${prefix}${word ? ' ' + word : ''}`);
isFirstLine = false;
}
else {
lines.push(`${indent}${word}`);
}
currentLine = '';
}
}
else {
currentLine = proposedLine;
}
}
// Add remaining content
if (currentLine) {
if (isFirstLine) {
lines.push(`${prefix}${currentLine ? ' ' + currentLine : ''}`);
}
else {
lines.push(`${indent}${currentLine}`);
}
}
// Handle empty prefix case
if (lines.length === 0) {
lines.push(prefix);
}
return lines;
}
/**
* Preprocess message to handle very long paths and URLs intelligently
*/
preprocessLongPaths(message) {
// Split very long file paths at natural breakpoints
return message.replace(/([\/\\][^\/\\:\s]{30,})/g, (match) => {
// Break long paths at directory separators
if (match.length > 60) {
const parts = match.split(/([\/\\])/);
let result = '';
let currentSegment = '';
for (const part of parts) {
if ((currentSegment + part).length > 50 && currentSegment) {
result += currentSegment + '\n' + part;
currentSegment = '';
}
else {
currentSegment += part;
}
}
result += currentSegment;
return result;
}
return match;
});
}
/**
* Strip ANSI color codes to get actual text length
*/
stripAnsiCodes(text) {
return text.replace(/\x1b\[[0-9;]*m/g, '');
}
detectErrorType(message, meta) {
// Check meta for explicit category first
if (meta.category) {
return meta.category;
}
// Handle only the MAJOR ones we have smart diagnostics for
// Enhanced detection for import vs syntax errors
if (message.includes('Cannot find module')) {
return this.detectImportVsSyntax(message, meta);
}
// Direct syntax error detection
if (message.includes('SyntaxError') ||
message.includes('Unexpected token') ||
message.includes('Unexpected end of input')) {
return 'syntax';
}
// Startup/infrastructure errors
if (message.includes('EADDRINUSE') ||
message.includes('CONTRACT') ||
message.includes('port') ||
message.includes('startup')) {
return 'startup';
}
// Route registration errors
if (message.includes('ROUTE') || message.includes('registration')) {
return 'route';
}
// For everything else - just show the raw error with general handling
return 'general';
}
detectImportVsSyntax(message, meta) {
const modulePath = message.match(/Cannot find module '([^']+)'/)?.[1];
if (modulePath && meta.feature) {
// Try to resolve the actual file path
const basePath = process.cwd();
const featurePath = join(basePath, 'src', 'features', meta.feature);
// Build possible file paths
const possiblePaths = [
join(featurePath, modulePath + '.ts'),
join(featurePath, modulePath + '.js'),
join(featurePath, modulePath, 'index.ts'),
join(featurePath, modulePath, 'index.js'),
// Also try relative to the file that's importing
modulePath + '.ts',
modulePath + '.js'
];
// Check if any of these files exist
for (const filePath of possiblePaths) {
try {
if (existsSync(filePath)) {
// File exists but import failed - likely syntax error preventing import
return 'syntax';
}
}
catch {
// Ignore file system errors, continue checking
}
}
}
// File doesn't exist - true import error
return 'import';
}
getErrorTitle(errorType) {
const titles = {
import: 'IMPORT ERROR',
syntax: 'SYNTAX ERROR',
startup: 'STARTUP ERROR',
contract: 'CONTRACT ERROR',
route: 'ROUTE ERROR',
filesystem: 'FILE SYSTEM ERROR',
general: 'ERROR'
};
return titles[errorType] || 'ERROR';
}
generateDiagnostics(message, meta, errorType) {
const diagnostics = [];
// Only provide smart diagnostics for errors we understand well
switch (errorType) {
case 'syntax':
if (message.includes('Unexpected token')) {
const tokenMatch = message.match(/Unexpected token '([^']+)'/);
if (tokenMatch) {
diagnostics.push({
type: 'error',
message: `Unexpected token: ${tokenMatch[1]}`,
});
}
}
if (message.includes('Unexpected end of input')) {
diagnostics.push({
type: 'error',
message: 'Missing closing bracket, brace, or parenthesis',
fix: 'Check for unclosed {}, [], or () brackets'
});
}
// Line number extraction
const lineMatch = message.match(/line (\d+)/i);
if (lineMatch) {
diagnostics.push({
type: 'info',
message: `Error location: line ${lineMatch[1]}`
});
}
break;
case 'import':
const missingModule = message.match(/Cannot find module '([^']+)'/)?.[1];
if (missingModule) {
diagnostics.push({
type: 'error',
message: `Missing import: ${missingModule}`,
});
// Check for common typos
if (missingModule.includes('Service') && missingModule.endsWith('Servic')) {
diagnostics.push({
type: 'warning',
message: 'Looks like a typo: missing \'e\' at end?',
fix: `Try: ${missingModule}e`
});
}
if (missingModule.includes('/services/')) {
diagnostics.push({
type: 'info',
message: 'Check service file exists and name matches'
});
}
}
break;
case 'startup':
if (message.includes('EADDRINUSE')) {
diagnostics.push({
type: 'error',
message: 'Port already in use',
fix: 'Change PORT environment variable or stop conflicting process'
});
}
if (message.includes('CONTRACT')) {
diagnostics.push({
type: 'error',
message: 'Contract validation failed',
fix: 'Run: npm run flux:check to see detailed contract issues'
});
}
break;
case 'route':
if (message.includes('export')) {
diagnostics.push({
type: 'error',
message: 'Route file must export a function as default',
fix: 'Add: export default router(...)'
});
}
break;
case 'general':
// For general errors, don't add diagnostics - just show the raw error
// This covers ReferenceError, TypeError, and all other JS errors
break;
}
return diagnostics;
}
getFixMessage(errorType, message) {
// Pattern-based error analysis - extract meaning from the error message itself
// 1. SYNTAX ERRORS - Parse what's actually expected/unexpected
const expectedMatch = message.match(/Expected "(.+?)" but found/i);
if (expectedMatch) {
return `Add missing "${expectedMatch[1]}"`;
}
const unexpectedTokenMatch = message.match(/Unexpected token "(.+?)"/i) || message.match(/Unexpected "(.+?)"/i);
if (unexpectedTokenMatch) {
return `Remove or fix unexpected "${unexpectedTokenMatch[1]}"`;
}
if (message.match(/Unexpected end of input/i)) {
return 'Add missing closing bracket, brace, or parenthesis';
}
// 2. IMPORT/MODULE ERRORS - Extract the actual module path
const moduleMatch = message.match(/Cannot find module ['"']([^'"]+)['"']/);
if (moduleMatch) {
const modulePath = moduleMatch[1];
const fileName = modulePath.split('/').pop() || modulePath;
return `Check if file "${fileName}" exists at path: ${modulePath}`;
}
// 3. REFERENCE ERRORS - Extract the undefined variable/function
const refErrorMatch = message.match(/(\w+) is not defined/);
if (refErrorMatch) {
return `Define variable or import "${refErrorMatch[1]}"`;
}
// 4. TYPE ERRORS - Extract specific type issues
const notFunctionMatch = message.match(/(\w+) is not a function/);
if (notFunctionMatch) {
return `Check that "${notFunctionMatch[1]}" is actually a function`;
}
const cannotReadMatch = message.match(/Cannot read propert(?:y|ies) ['"]?(\w+)['"]? of/);
if (cannotReadMatch) {
return `Check object exists before accessing property "${cannotReadMatch[1]}"`;
}
const cannotAccessMatch = message.match(/Cannot access ['"]?(\w+)['"]? before initialization/);
if (cannotAccessMatch) {
return `Move "${cannotAccessMatch[1]}" declaration before usage`;
}
// 5. NETWORK/CONNECTION ERRORS - Extract connection details
const econnrefusedMatch = message.match(/ECONNREFUSED.*?(\d+)/);
if (econnrefusedMatch) {
return `Connection refused on port ${econnrefusedMatch[1]} - check if service is running`;
}
if (message.match(/EADDRINUSE.*?(\d+)/)) {
const portMatch = message.match(/(\d+)/);
return portMatch ? `Port ${portMatch[1]} already in use - choose different port` : 'Port already in use';
}
// 6. FILE SYSTEM ERRORS - Extract file/directory info
const enoentMatch = message.match(/ENOENT.*?['"]([^'"]+)['"]/) || message.match(/no such file or directory.*?['"]([^'"]+)['"]/);
if (enoentMatch) {
return `File not found: ${enoentMatch[1]}`;
}
const eaccesMatch = message.match(/EACCES.*?['"]([^'"]+)['"]/) || message.match(/permission denied.*?['"]([^'"]+)['"]/);
if (eaccesMatch) {
return `Permission denied accessing: ${eaccesMatch[1]}`;
}
// 7. COMPILATION/TRANSFORM ERRORS - Extract compilation context
if (message.match(/Transform failed|compilation failed/i)) {
const lineMatch = message.match(/line (\d+)/i) || message.match(/:(\d+):/);
return lineMatch ? `Fix compilation error at line ${lineMatch[1]}` : 'Fix compilation/syntax errors';
}
// 8. PROMISE/ASYNC ERRORS
if (message.match(/UnhandledPromiseRejection/i)) {
return 'Add .catch() handler or try/catch block for async operation';
}
// 9. EXPORT/IMPORT ERRORS - Extract export context
if (message.match(/default.*?export/i) || message.match(/export.*?default/i)) {
return 'Fix export default statement syntax';
}
// 10. LINE-SPECIFIC ERRORS - Extract line numbers when available
const lineNumberMatch = message.match(/(?:line |:)(\d+)(?::(\d+))?/i);
if (lineNumberMatch) {
const line = lineNumberMatch[1];
const column = lineNumberMatch[2];
const location = column ? `line ${line}, column ${column}` : `line ${line}`;
if (message.match(/syntax|unexpected|expected/i)) {
return `Fix syntax error at ${location}`;
}
return `Check code at ${location}`;
}
// 11. GENERIC PATTERNS - Fallback pattern matching
if (message.match(/not found|missing/i)) {
return 'Check that required files/resources exist';
}
if (message.match(/timeout|timed out/i)) {
return 'Increase timeout or check network connectivity';
}
if (message.match(/invalid|malformed/i)) {
return 'Check data format and syntax';
}
if (message.match(/denied|forbidden/i)) {
return 'Check permissions and access rights';
}
// 12. FRAMEWORK-AGNOSTIC FALLBACKS by error type
switch (errorType) {
case 'import':
return 'Verify import paths and file existence';
case 'syntax':
return 'Fix JavaScript/TypeScript syntax errors';
case 'startup':
return 'Fix configuration or dependency issues';
case 'route':
return 'Check export statements and file structure';
case 'general':
return 'Review error details above';
default:
return 'Fix the error described above';
}
}
/**
* Get contextual solutions based on error message patterns
*/
getSolutions(errorType, message) {
const solutions = [];
// Extract solutions based on actual error content
if (message.match(/Expected "(.+?)" but found/)) {
solutions.push('Check for missing brackets, parentheses, or braces');
solutions.push('Verify function call syntax');
}
if (message.match(/Cannot find module/)) {
solutions.push('Check if file exists at the specified path');
solutions.push('Verify import path spelling');
solutions.push('Check file extensions (.js, .ts)');
}
if (message.match(/is not defined/)) {
solutions.push('Import the required module or variable');
solutions.push('Check variable name spelling');
solutions.push('Verify variable is declared before use');
}
if (message.match(/is not a function/)) {
solutions.push('Check that imported item is actually a function');
solutions.push('Verify function is exported correctly');
}
if (message.match(/EADDRINUSE/)) {
solutions.push('Change to a different port number');
solutions.push('Stop the process using this port');
solutions.push('Check for other running servers');
}
if (message.match(/line \d+/i)) {
const lineMatch = message.match(/line (\d+)/i);
if (lineMatch) {
solutions.push(`Check syntax around line ${lineMatch[1]}`);
solutions.push('Look for missing commas or semicolons');
}
}
// Add generic solutions if none were found
if (solutions.length === 0) {
const defaultSolutions = {
import: ['Verify file paths and existence', 'Check import syntax'],
syntax: ['Fix JavaScript/TypeScript syntax', 'Check for missing punctuation'],
startup: ['Review configuration files', 'Check dependencies'],
route: ['Verify export statements', 'Check file structure'],
general: ['Review error message details', 'Check recent code changes']
};
solutions.push(...(defaultSolutions[errorType] || defaultSolutions.general));
}
return solutions;
}
// ============================================================================
// EXISTING HELPER METHODS
// ============================================================================
getCaller() {
const stack = new Error().stack;
if (!stack)
return 'unknown';
const lines = stack.split('\n');
const callerLine = lines[4] || '';
const match = callerLine.match(/\((.+):(\d+):(\d+)\)/) ||
callerLine.match(/at\s+(.+):(\d+):(\d+)/);
if (match) {
const [, filePath, lineNumber] = match;
const cleanPath = filePath.replace(process.cwd() + '/', '');
return `${cleanPath}:${lineNumber}`;
}
return 'unknown';
}
log(level, message, meta) {
if (LOG_LEVELS[level] > this.levelValue) {
return;
}
const entry = {
timestamp: new Date().toISOString(),
level,
message: message || '',
...this.defaultMeta,
...meta,
};
this.writeToTransports(entry);
}
writeToTransports(entry) {
const writePromises = [];
for (const [name, transport] of this.transports) {
try {
if (transport.shouldLog && !transport.shouldLog(entry.level, this.level)) {
continue;
}
const result = transport.write(entry);
if (result && typeof result.then === 'function') {
writePromises.push(result.catch((error) => {
console.error(`Transport ${name} write failed:`, error.message);
}));
}
}
catch (error) {
console.error(`Transport ${name} write error:`, error.message);
}
}
this.pendingWrites = writePromises;
}
// ============================================================================
// PUBLIC UTILITY METHODS
// ============================================================================
/**
* Flush pending logs to all transports
* @llm-rule WHEN: App shutdown or ensuring logs are persisted before critical operations
* @llm-rule AVOID: Frequent flushing during normal operations - impacts performance
*/
async flush() {
if (this.pendingWrites.length > 0) {
try {
await Promise.all(this.pendingWrites);
}
catch {
// Errors already handled in writeToTransports
}
this.pendingWrites = [];
}
const flushPromises = [];
for (const [name, transport] of this.transports) {
if (transport.flush) {
try {
const result = transport.flush();
if (result) {
flushPromises.push(result.catch((error) => {
console.error(`Transport ${name} flush failed:`, error.message);
}));
}
}
catch (error) {
console.error(`Transport ${name} flush error:`, error.message);
}
}
}
if (flushPromises.length > 0) {
await Promise.all(flushPromises);
}
}
/**
* Close logger and cleanup all transports
* @llm-rule WHEN: App shutdown or logger cleanup - ensures graceful resource cleanup
* @llm-rule AVOID: Calling during normal operations - this permanently closes the logger
*/
async close() {
await this.flush();
const closePromises = [];
for (const [name, transport] of this.transports) {
if (transport.close) {
try {
const result = transport.close();
if (result) {
closePromises.push(result.catch((error) => {
console.error(`Transport ${name} close failed:`, error.message);
}));
}
}
catch (error) {
console.error(`Transport ${name} close error:`, error.message);
}
}
}
if (closePromises.length > 0) {
await Promise.all(closePromises);
}
this.transports.clear();
}
/**
* Get list of active transport names
* @llm-rule WHEN: Debugging logger setup or checking which transports are running
* @llm-rule AVOID: Using for business logic - this is for debugging and monitoring only
*/
getActiveTransports() {
return Array.from(this.transports.keys());
}
/**
* Check if specific transport is active
* @llm-rule WHEN: Conditionally logging based on transport availability
* @llm-rule AVOID: Complex transport detection - just log normally, transports auto-enable
*/
hasTransport(name) {
return this.transports.has(name);
}
/**
* Set minimum log level dynamically
* @llm-rule WHEN: Runtime log level changes, debugging, or feature flags
* @llm-rule AVOID: Frequent level changes - impacts performance
*/
setLevel(level) {
this.level = level;
this.levelValue = LOG_LEVELS[level];
}
/**
* Get current minimum log level
* @llm-rule WHEN: Debugging configuration or checking level settings
* @llm-rule AVOID: Using for filtering logic - logger handles level filtering automatically
*/
getLevel() {
return this.level;
}
/**
* Check if specific log level would be written
* @llm-rule WHEN: Expensive log message preparation - check before building complex meta
* @llm-rule AVOID: Normal logging - level filtering is automatic and fast
*/
isLevelEnabled(level) {
return LOG_LEVELS[level] <= this.levelValue;
}
/**
* Get current logger configuration for debugging
* @llm-rule WHEN: Debugging logger setup, checking environment detection, or monitoring
* @llm-rule AVOID: Using for runtime business logic - configuration is set at startup
*/
getConfig() {
return {
level: this.level,
scope: this.config.scope,
minimal: this.config.minimal,
transports: this.getActiveTransports(),
service: this.config.service,
environment: {
NODE_ENV: process.env.NODE_ENV,
hasDbUrl: !!process.env.DATABASE_URL,
hasHttpUrl: !!process.env.VOILA_LOGGER_HTTP_URL,
hasWebhookUrl: !!process.env.VOILA_LOGGER_WEBHOOK_URL,
},
};
}
}
//# sourceMappingURL=logger.js.map