@voilajsx/appkit
Version:
Minimal and framework agnostic Node.js toolkit designed for AI agentic backend development
213 lines • 8.94 kB
JavaScript
/**
* Smart defaults with direct environment access and auto transport detection
* @module @voilajsx/appkit/logger
* @file src/logger/defaults.ts
*
* @llm-rule WHEN: App startup - need production-ready logging configuration
* @llm-rule AVOID: Calling multiple times - expensive environment parsing, cache results
* @llm-rule NOTE: Called once at startup, cached globally for performance like auth module
* @llm-rule NOTE: Now includes visual error configuration for enhanced developer experience
*/
/**
* Get smart defaults using direct VOILA_LOGGER_* environment access
* @llm-rule WHEN: App startup to get production-ready logging configuration
* @llm-rule AVOID: Calling repeatedly - validates environment each time, expensive operation
* @llm-rule NOTE: Called once at startup, cached globally for performance
*/
export function getSmartDefaults() {
// Direct environment access with smart defaults (like auth module)
const nodeEnv = process.env.NODE_ENV || 'development';
const isProduction = nodeEnv === 'production';
const isDevelopment = nodeEnv === 'development';
const isTest = nodeEnv === 'test';
// Auto-detect logging scope
const scope = getScope();
const minimal = scope === 'minimal';
// Auto-detect log level
const level = getLevel(isProduction, isDevelopment);
// Auto-detect enabled transports
const transports = getEnabledTransports(isTest);
return {
level,
scope,
minimal,
transports,
// Console config - direct env access
console: {
colorize: process.env.VOILA_LOGGER_CONSOLE_COLOR !== 'false' && !isProduction,
timestamps: process.env.VOILA_LOGGER_CONSOLE_TIME !== 'false',
prettyPrint: isDevelopment && !minimal,
},
// File config - direct env access
file: {
dir: process.env.VOILA_LOGGER_DIR || 'logs',
filename: process.env.VOILA_LOGGER_FILE_NAME || 'app.log',
maxSize: parseInt(process.env.VOILA_LOGGER_FILE_SIZE || (isProduction ? '50000000' : '10000000')),
retentionDays: parseInt(process.env.VOILA_LOGGER_FILE_RETENTION || (isProduction ? '30' : '7')),
},
// Database config - direct env access
database: {
url: process.env.DATABASE_URL || null,
table: process.env.VOILA_LOGGER_DB_TABLE || 'logs',
batchSize: parseInt(process.env.VOILA_LOGGER_DB_BATCH || (minimal ? '50' : '100')),
},
// HTTP config - direct env access
http: {
url: process.env.VOILA_LOGGER_HTTP_URL || null,
batchSize: parseInt(process.env.VOILA_LOGGER_HTTP_BATCH || (minimal ? '25' : '50')),
timeout: parseInt(process.env.VOILA_LOGGER_HTTP_TIMEOUT || '30000'),
},
// Webhook config - direct env access
webhook: {
url: process.env.VOILA_LOGGER_WEBHOOK_URL || null,
level: process.env.VOILA_LOGGER_WEBHOOK_LEVEL || 'error',
rateLimit: parseInt(process.env.VOILA_LOGGER_WEBHOOK_RATE || (minimal ? '5' : '10')),
},
// Service identification - direct env access
service: {
name: process.env.VOILA_SERVICE_NAME || process.env.npm_package_name || 'app',
version: process.env.VOILA_SERVICE_VERSION || process.env.npm_package_version || '1.0.0',
environment: nodeEnv,
},
};
}
/**
* Auto-detect optimal logging scope
* @llm-rule WHEN: Need to determine minimal vs full logging automatically
* @llm-rule AVOID: Manual scope selection - auto-detection handles most cases correctly
*/
function getScope() {
// Manual override wins (like auth module pattern)
const manual = process.env.VOILA_LOGGER_SCOPE?.toLowerCase();
if (manual === 'minimal' || manual === 'full') {
return manual;
}
// Auto-detection logic
if (process.env.CI)
return 'minimal'; // CI/CD pipelines
if (process.env.NODE_ENV === 'production')
return 'minimal'; // Production efficiency
if (process.env.DEBUG || process.env.VOILA_DEBUG)
return 'full'; // Debug sessions
return 'minimal'; // Safe default for clean logs
}
/**
* Auto-detect appropriate log level
* @llm-rule WHEN: Need to set log level based on environment automatically
* @llm-rule AVOID: Hardcoding log levels - environment-based detection is better
*/
function getLevel(isProduction, isDevelopment) {
// Manual override wins (like auth module)
const manual = process.env.VOILA_LOGGER_LEVEL?.toLowerCase();
if (manual && ['debug', 'info', 'warn', 'error'].includes(manual)) {
return manual;
}
// Smart defaults based on environment
if (isProduction)
return 'warn'; // Production: only warnings and errors
if (isDevelopment)
return 'debug'; // Development: everything
return 'info'; // Test and others: standard
}
/**
* Auto-detect enabled transports from environment
* @llm-rule WHEN: Need to determine which transports to enable automatically
* @llm-rule AVOID: Manual transport configuration - auto-detection prevents errors
* @llm-rule NOTE: DATABASE_URL auto-enables database, WEBHOOK_URL auto-enables webhooks
*/
function getEnabledTransports(isTest) {
return {
// Console: default on (except test)
console: process.env.VOILA_LOGGER_CONSOLE !== 'false' && !isTest,
// File: default on (except test)
file: process.env.VOILA_LOGGER_FILE !== 'false' && !isTest,
// Database: auto-enable if DATABASE_URL exists
database: process.env.VOILA_LOGGER_DATABASE === 'true' && !!process.env.DATABASE_URL,
// HTTP: auto-enable if URL provided
http: !!process.env.VOILA_LOGGER_HTTP_URL,
// Webhook: auto-enable if URL provided
webhook: !!process.env.VOILA_LOGGER_WEBHOOK_URL,
};
}
/**
* Validate environment variables (like auth module validation)
* @llm-rule WHEN: App startup to catch configuration errors early
* @llm-rule AVOID: Skipping validation - invalid config causes silent failures
*/
export function validateEnvironment() {
// Validate log level
const level = process.env.VOILA_LOGGER_LEVEL;
if (level && !['debug', 'info', 'warn', 'error'].includes(level)) {
throw new Error(`Invalid VOILA_LOGGER_LEVEL: "${level}". Must be: debug, info, warn, error`);
}
// Validate scope
const scope = process.env.VOILA_LOGGER_SCOPE;
if (scope && !['minimal', 'full'].includes(scope.toLowerCase())) {
throw new Error(`Invalid VOILA_LOGGER_SCOPE: "${scope}". Must be: minimal, full`);
}
// Validate visual errors setting
const visualErrors = process.env.VOILA_VISUAL_ERRORS;
if (visualErrors && !['true', 'false'].includes(visualErrors)) {
throw new Error(`Invalid VOILA_VISUAL_ERRORS: "${visualErrors}". Must be: true, false`);
}
// Validate URLs if provided
const httpUrl = process.env.VOILA_LOGGER_HTTP_URL;
if (httpUrl && !isValidUrl(httpUrl)) {
throw new Error(`Invalid VOILA_LOGGER_HTTP_URL: "${httpUrl}"`);
}
const webhookUrl = process.env.VOILA_LOGGER_WEBHOOK_URL;
if (webhookUrl && !isValidUrl(webhookUrl)) {
throw new Error(`Invalid VOILA_LOGGER_WEBHOOK_URL: "${webhookUrl}"`);
}
// Validate database URL if database logging enabled
const dbEnabled = process.env.VOILA_LOGGER_DATABASE === 'true';
const dbUrl = process.env.DATABASE_URL;
if (dbEnabled && !dbUrl) {
throw new Error('VOILA_LOGGER_DATABASE=true but DATABASE_URL not provided');
}
if (dbUrl && !isValidDatabaseUrl(dbUrl)) {
throw new Error(`Invalid DATABASE_URL: "${dbUrl}"`);
}
// Validate numeric values
validateNumericEnv('VOILA_LOGGER_FILE_SIZE', 1000000, 100000000); // 1MB to 100MB
validateNumericEnv('VOILA_LOGGER_FILE_RETENTION', 1, 365); // 1 to 365 days
validateNumericEnv('VOILA_LOGGER_HTTP_TIMEOUT', 1000, 300000); // 1s to 5min
}
/**
* Validate numeric environment variable
*/
function validateNumericEnv(name, min, max) {
const value = process.env[name];
if (!value)
return;
const num = parseInt(value);
if (isNaN(num) || num < min || num > max) {
throw new Error(`Invalid ${name}: "${value}". Must be number between ${min} and ${max}`);
}
}
/**
* Validate URL format (like auth module)
*/
function isValidUrl(url) {
try {
const parsed = new URL(url);
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
}
catch {
return false;
}
}
/**
* Validate database URL format
*/
function isValidDatabaseUrl(url) {
try {
const parsed = new URL(url);
const validProtocols = ['postgres:', 'postgresql:', 'mysql:', 'sqlite:'];
return validProtocols.includes(parsed.protocol);
}
catch {
return false;
}
}
//# sourceMappingURL=defaults.js.map