@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
JavaScript
/**
* 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