UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

260 lines 9.87 kB
/** * Pino logger implementation with environment-specific transport support */ import { existsSync, mkdirSync } from 'fs'; import { join } from 'path'; import pino from 'pino'; import { createConfig, getDefaultLogDirectory } from './config.js'; import { createLogMethods } from './log-method-factory.js'; import { createRedactionRules, redactLogEntry } from './redaction.js'; /** * Type guard to check if a value is a Promise * Handles void returns properly by checking for specific Promise characteristics */ function _isPromise(value) { return (value !== null && value !== undefined && typeof value === 'object' && 'then' in value); } /** * Determine transport configuration based on environment and CLI settings * Currently returns a fixed configuration for all environments (file logging only) */ function determineTransportConfig() { // All environments: file only, no console - simplified approach return { enableFile: true, // Always enable file logging enableConsole: false, // Never use console transport }; } /** * Create unified logger using file transport for all environments */ function createEnvironmentLogger(baseConfig, transportConfig) { const logDir = getDefaultLogDirectory(); // Create single file transport logger for all environments if (transportConfig.enableFile && !transportConfig.enableConsole) { // Ensure directory exists if (!existsSync(logDir)) { mkdirSync(logDir, { recursive: true }); } // Use Intl.DateTimeFormat for local timezone-aware date formatting const now = new Date(); const localDate = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit', timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, }) .format(now) .replace(/\//g, '-'); const logFilePath = join(logDir, `nanocoder-${localDate}.log`); // Use pino.destination() instead of pino.transport() for synchronous flush support const destination = pino.destination({ dest: logFilePath, sync: false, // Async writes for performance mkdir: true, }); const pinoLogger = pino(baseConfig, destination); const redactionRules = createRedactionRules(Array.isArray(baseConfig.redact) ? baseConfig.redact : [], true, // Enable email redaction true); return createEnhancedLogger(pinoLogger, destination, redactionRules); } // This should never be reached with current configuration // If we get here, it means determineTransportConfig returned an invalid config throw new Error('Invalid transport configuration: enableFile must be true and enableConsole must be false'); } /** * Create enhanced logger with correlation and redaction support */ function createEnhancedLogger(pinoLogger, destination, redactionRules) { // Create a transformer for Pino logger with redaction rules const createPinoTransformer = (_level) => { return (args, _msg) => { // Apply redaction to object arguments if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null && redactionRules) { args[0] = redactLogEntry(args[0], redactionRules); } return args; }; }; // Use the factory to create all log methods const logMethods = createLogMethods(pinoLogger, { transformArgs: createPinoTransformer(''), }); return { ...logMethods, child: (bindings) => { return createEnhancedChild(pinoLogger, bindings, redactionRules); }, isLevelEnabled: (level) => { return pinoLogger.isLevelEnabled(level); }, flush: async () => { if (destination && 'flush' in destination) { const flushMethod = destination.flush; if (flushMethod && typeof flushMethod === 'function') { flushMethod(); } } }, flushSync: () => { if (destination && 'flushSync' in destination) { const flushSyncMethod = destination.flushSync; if (flushSyncMethod && typeof flushSyncMethod === 'function') { flushSyncMethod(); } } }, end: async () => { if (destination && 'end' in destination) { const endMethod = destination.end; if (endMethod && typeof endMethod === 'function') { try { destination.end(); } catch (error) { // Ignore errors when ending an already-closed or invalid stream // This can happen when end() is called multiple times or the destination // is in an invalid state (e.g., during test cleanup) const errorMsg = error instanceof Error ? error.message : String(error); // Only log unexpected errors, not "destroyed" property access errors if (!errorMsg.includes('destroyed')) { console.warn(`[Logger] Warning: Error ending destination stream: ${errorMsg}`); } } } } }, // Store destination for direct access if needed _destination: destination, }; } /** * Create enhanced child logger with correlation and redaction */ function createEnhancedChild(parent, bindings, redactionRules) { const child = parent.child(bindings); // Create a transformer for Pino logger with redaction rules const createPinoTransformer = (_level) => { return (args, _msg) => { // Apply redaction to object arguments if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null && redactionRules) { args[0] = redactLogEntry(args[0], redactionRules); } return args; }; }; // Use the factory to create all log methods const logMethods = createLogMethods(child, { transformArgs: createPinoTransformer(''), }); return { ...logMethods, child: (moreBindings) => { return createEnhancedChild(child, moreBindings, redactionRules); }, isLevelEnabled: (level) => { return child.isLevelEnabled(level); }, flush: async () => { // Child loggers don't have direct access to destination // Flush is a no-op for children }, flushSync: () => { // Child loggers don't have direct access to destination // FlushSync is a no-op for children }, end: async () => { // Child loggers don't have direct access to destination // End is a no-op for children }, }; } /** * Create a Pino logger with environment-specific transports and CLI configuration */ export function createPinoLogger(config) { const finalConfig = createConfig(config); // Determine transport configuration const transportConfig = determineTransportConfig(); // Base Pino configuration with updated fields const baseConfig = { level: finalConfig.level, redact: finalConfig.redact, formatters: { level: (label, _number) => ({ level: label.toUpperCase() }), }, timestamp: pino.stdTimeFunctions.isoTime, base: { pid: process.pid, platform: process.platform, arch: process.arch, service: 'nanocoder', version: process.env.npm_package_version || 'unknown', environment: process.env.NODE_ENV || 'production', nodeVersion: process.version, }, }; // Create environment-specific logger using transports const logger = createEnvironmentLogger(baseConfig, transportConfig); return logger; } /** * Create a logger with custom transport configuration (for advanced usage) */ export function createLoggerWithTransport(config, transport) { const finalConfig = createConfig(config); // Handle transport parameter let actualTransport; if (transport) { if (typeof transport === 'object' && 'target' in transport) { actualTransport = pino.transport(transport); } else { actualTransport = transport; } } const pinoConfig = { level: finalConfig.level, redact: finalConfig.redact, formatters: { level: (label, _number) => ({ level: label.toUpperCase() }), }, base: { pid: process.pid, platform: process.platform, arch: process.arch, service: 'nanocoder', version: process.env.npm_package_version || 'unknown', environment: process.env.NODE_ENV || 'production', nodeVersion: process.version, }, }; const pinoLogger = actualTransport ? pino(pinoConfig, actualTransport) : pino(pinoConfig); const redactionRules = createRedactionRules(finalConfig.redact, true, // Enable email redaction true); return createEnhancedLogger(pinoLogger, actualTransport, redactionRules); } /** * Get logger statistics */ export function getLoggerStats() { const config = createConfig(); const environment = process.env.NODE_ENV || 'production'; return { level: config.level, silent: config.level === 'silent', environment, }; } //# sourceMappingURL=pino-logger.js.map