@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
253 lines • 7.96 kB
JavaScript
/**
* Transport configuration for different output destinations
*/
import { join } from 'path';
import { BUFFER_LOG_BYTES, INTERVAL_LOG_FLUSH_MS } from '../../constants.js';
import { getDefaultLogDirectory } from './config.js';
/**
* Create a development transport with pretty printing
*/
export function createDevelopmentTransport() {
return {
target: 'pino-pretty',
level: 'debug',
options: {
colorize: true,
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
levelFirst: true,
messageFormat: '{levelLabel} - {msg}',
customPrettifiers: {
time: (timestamp) => {
return new Date(timestamp).toLocaleTimeString('en-US', {
hour12: false,
timeZone: 'UTC',
});
},
},
singleLine: false,
},
};
}
/**
* Create a production transport with file rotation
*/
export function createProductionTransport(logDir = getDefaultLogDirectory()) {
return {
target: 'pino-roll',
level: 'info',
options: {
file: join(logDir, 'nanocoder-%Y-%m-%d.log'), // nosemgrep
frequency: 'daily',
size: '100m',
dateFormat: 'yyyy-MM-dd',
extension: '.log',
symlink: true,
mkdir: true,
compress: true,
sync: false,
limit: {
count: 30,
removeOtherLogFiles: true,
},
minLength: 4096,
maxLength: 1048576, // 1MB
periodicFlush: INTERVAL_LOG_FLUSH_MS,
},
};
}
/**
* Create a test transport that outputs to /dev/null
*/
export function createTestTransport() {
return {
target: 'pino/file',
level: 'silent',
options: {
destination: '/dev/null',
},
};
}
/**
* Create a custom transport for specific needs
*/
export function createCustomTransport(config) {
return {
target: config.target,
level: config.options?.level || 'info',
options: {
...config.options,
// Ensure minimum buffer size for performance
minLength: config.options.minLength || 4096,
// Set maximum buffer size to prevent memory issues
maxLength: config.options.maxLength || 1048576,
},
};
}
/**
* Create a multi-transport configuration
*/
export function createMultiTransport() {
const transports = [];
const env = process.env.NODE_ENV || 'development';
// Always include console output for development
if (env !== 'production') {
transports.push(createDevelopmentTransport());
}
// Add file output for production or when explicitly enabled
if (env === 'production' || process.env.NANOCODER_LOG_TO_FILE === 'true') {
transports.push(createProductionTransport());
}
return transports;
}
/**
* Create a transport with buffering for high-performance scenarios
*/
export function createBufferedTransport(baseTransport, bufferSize = BUFFER_LOG_BYTES) {
return {
...baseTransport,
options: {
...baseTransport.options,
// Use sonic-boom for high-performance buffering
bufferSize,
// Enable async writing
sync: false,
},
};
}
/**
* Create a transport for error-specific logging
*/
export function createErrorTransport(logDir = getDefaultLogDirectory()) {
return {
target: 'pino-roll',
level: 'error',
options: {
file: join(logDir, 'nanocoder-error-%Y-%m-%d.log'), // nosemgrep
frequency: 'daily',
size: '50m', // Smaller files for errors
dateFormat: 'yyyy-MM-dd',
extension: '.log',
mkdir: true,
compress: true,
sync: true, // Sync writes for errors to ensure they're logged
limit: {
count: 90, // Keep more error logs
removeOtherLogFiles: true,
},
minLength: 1024,
maxLength: 1048576,
},
};
}
/**
* Create a transport for audit logging
*/
export function createAuditTransport(logDir = getDefaultLogDirectory()) {
return {
target: 'pino-roll',
level: 'info',
options: {
file: join(logDir, 'nanocoder-audit-%Y-%m-%d.log'), // nosemgrep
frequency: 'daily',
size: '200m',
dateFormat: 'yyyy-MM-dd',
extension: '.log',
mkdir: true,
compress: true,
sync: true, // Sync writes for audit logs
limit: {
count: 365, // Keep 1 year of audit logs
removeOtherLogFiles: false,
},
minLength: 1024,
maxLength: 10485760, // 10MB for audit logs
},
};
}
/**
* Get transport configuration based on environment variables
*/
export function getTransportFromEnvironment() {
// Support multiple transports via comma separation
const transportTypes = (process.env.NANOCODER_LOG_TRANSPORTS || 'default')
.split(',')
.map(t => t.trim());
const transports = [];
for (const transportType of transportTypes) {
switch (transportType) {
case 'development':
case 'dev':
transports.push(createDevelopmentTransport());
break;
case 'production':
case 'prod':
transports.push(createProductionTransport());
break;
case 'test':
transports.push(createTestTransport());
break;
case 'error':
transports.push(createErrorTransport());
break;
case 'audit':
transports.push(createAuditTransport());
break;
case 'default':
default:
// Use default behavior
{
const defaultTransports = createMultiTransport();
transports.push(...defaultTransports);
}
break;
}
}
// Return single transport if only one, otherwise return array
return transports.length === 1 ? transports[0] : transports;
}
/**
* Validate transport configuration
*/
export function validateTransport(transport) {
if (!transport.target) {
console.error('[ERROR] Transport target is required');
return false;
}
// Validate target is a string or function
if (typeof transport.target !== 'string' &&
typeof transport.target !== 'function') {
console.error('[ERROR] Transport target must be a string or function');
return false;
}
// Validate options if present
if (transport.options && typeof transport.options !== 'object') {
console.error('[ERROR] Transport options must be an object');
return false;
}
return true;
}
/**
* Create transport with error handling and fallback
*/
export function createSafeTransport(primaryTransport, fallbackTransport) {
try {
if (!validateTransport(primaryTransport)) {
throw new Error('Invalid primary transport configuration');
}
return primaryTransport;
}
catch (error) {
console.error('[ERROR] Failed to create primary transport:', error);
if (fallbackTransport) {
console.warn('[WARN] Falling back to secondary transport');
if (validateTransport(fallbackTransport)) {
return fallbackTransport;
}
}
// Ultimate fallback to console
console.warn('[WARN] Falling back to console transport');
return createDevelopmentTransport();
}
}
//# sourceMappingURL=transports.js.map