UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

492 lines 17.7 kB
/** * NeuroLink Unified Logger Utility * * Centralized logging for the entire NeuroLink ecosystem. * Provides structured logging with different severity levels and consistent formatting. * Supports both CLI --debug flag and NEUROLINK_DEBUG environment variable. * Maintains compatibility with MCP logging while providing enhanced features. * * Features: * - Multiple log levels (debug, info, warn, error) * - Log history retention with configurable limits * - Conditional logging based on environment settings * - Structured data support for complex objects * - Tabular data display */ // OTel trace context for log correlation (optional — gracefully no-ops if OTel not initialized) let traceApi = null; let traceApiPromise = null; async function getTraceApi() { if (!traceApiPromise) { traceApiPromise = import("@opentelemetry/api") .then((mod) => { traceApi = mod; return mod; }) .catch(() => null); } return traceApiPromise; } // Eagerly kick off the import so the cached value is available for synchronous callers void getTraceApi(); // Pre-computed uppercase log levels for performance optimization const UPPERCASE_LOG_LEVELS = { debug: "DEBUG", info: "INFO", warn: "WARN", error: "ERROR", }; class NeuroLinkLogger { logLevel = "info"; logs = []; maxLogs = 1000; isDebugMode; eventEmitter; constructor() { // Cache debug mode check to avoid repeated array searches this.isDebugMode = process.argv.includes("--debug") || process.env.NEUROLINK_DEBUG === "true"; // Check NEUROLINK_LOG_LEVEL for consistency with the unified NeuroLink logger const envLevel = process.env.NEUROLINK_LOG_LEVEL?.toLowerCase(); if (envLevel && ["debug", "info", "warn", "error"].includes(envLevel)) { this.logLevel = envLevel; } } /** * Sets the event emitter that will receive log events. * When set, all log operations will emit a "log-event" event. * * @param emitter - The event emitter instance */ setEventEmitter(emitter) { this.eventEmitter = emitter; } /** * Clears the event emitter reference. * Should be called when a NeuroLink instance is disposed to prevent memory leaks. */ clearEventEmitter() { this.eventEmitter = undefined; } /** * Sets the minimum log level that will be processed and output. * Log messages with a level lower than this will be ignored. * * @param level - The minimum log level to process ("debug", "info", "warn", or "error") */ setLogLevel(level) { this.logLevel = level; } /** * Determines whether a message with the given log level should be processed. * This method considers both the configured log level and the current debug mode. * * Logic: * 1. If not in debug mode, only error messages are allowed * 2. If in debug mode, messages at or above the configured log level are allowed * * @param level - The log level to check * @returns True if a message with this level should be logged, false otherwise */ shouldLog(level) { // Dynamic debug mode check to handle CLI middleware timing const currentDebugMode = process.argv.includes("--debug") || process.env.NEUROLINK_DEBUG === "true"; // Hide all logs except errors unless debugging if (!currentDebugMode && level !== "error") { return false; } const levels = ["debug", "info", "warn", "error"]; return levels.indexOf(level) >= levels.indexOf(this.logLevel); } /** * Generates a standardized prefix for log messages. * The prefix includes a timestamp and the log level in a consistent format. * * @param timestamp - ISO string representation of the log timestamp * @param level - The log level for this message * @returns Formatted prefix string like "[2025-08-18T13:45:30.123Z] [NEUROLINK:ERROR]" */ getLogPrefix(timestamp, level) { return `[${timestamp}] [NEUROLINK:${UPPERCASE_LOG_LEVELS[level]}]`; } /** * Extracts current OTel trace context (trace_id, span_id) if available. * Returns empty object if OTel is not initialized or no active span exists. */ getTraceContext() { if (!traceApi) { return {}; } try { const span = traceApi.trace.getSpan(traceApi.context.active()); if (!span) { return {}; } const spanContext = span.spanContext(); if (!spanContext || spanContext.traceId === "00000000000000000000000000000000") { return {}; } return { trace_id: spanContext.traceId, span_id: spanContext.spanId, trace_flags: String(spanContext.traceFlags), }; } catch { return {}; } } /** * Safely serialize data to fully expanded JSON string. * Handles circular references and non-serializable values. * Zero truncation — all nested objects and arrays are fully expanded. */ serializeData(data) { if (data === undefined || data === null) { return String(data); } if (typeof data !== "object") { return String(data); } try { return JSON.stringify(data, (_key, value) => { if (value instanceof Error) { return { name: value.name, message: value.message, stack: value.stack, }; } return value; }); } catch { // Handle circular references using a stack-based approach // to avoid false "[Circular]" on diamond (shared) references const ancestors = []; try { return JSON.stringify(data, function (_key, value) { if (value instanceof Error) { return { name: value.name, message: value.message, stack: value.stack, }; } if (typeof value === "object" && value !== null) { // Only flag actual circular (ancestor) references if (ancestors.includes(value)) { return "[Circular]"; } ancestors.push(value); } return value; }); } catch { return "[Unserializable Object]"; } } } /** * Outputs a log entry to the console based on the log level. * Data is fully serialized to JSON — no [Object] or [Array] truncation. * * @param level - The log level (debug, info, warn, error). * @param prefix - The formatted log prefix. * @param message - The log message. * @param data - Optional additional data to log. */ outputToConsole(level, prefix, message, data) { const logMethod = { debug: console.debug, info: console.info, warn: console.warn, error: console.error, }[level]; const traceCtx = this.getTraceContext(); const tracePrefix = traceCtx.trace_id ? ` [trace_id=${traceCtx.trace_id} span_id=${traceCtx.span_id}]` : ""; if (data !== undefined && data !== null) { logMethod(prefix + tracePrefix, message, this.serializeData(data)); } else { logMethod(prefix + tracePrefix, message); } } /** * Core internal logging method that handles: * 1. Creating log entries with consistent format * 2. Storing entries in the log history * 3. Managing log rotation to prevent memory issues * 4. Outputting formatted logs to the console * 5. Emitting log events if an event emitter is configured * * This is the central method called by all specific logging methods (debug, info, etc.) * * @param level - The severity level for this log entry * @param message - The message text to log * @param data - Optional additional context data to include */ log(level, message, data) { if (!this.shouldLog(level)) { return; } const entry = { level, message, timestamp: new Date(), data, }; // Emit log event if emitter is configured if (this.eventEmitter) { try { this.eventEmitter.emit("log-event", { level, message, timestamp: new Date().getTime(), data, }); } catch { // Silently ignore emitter errors to avoid disrupting logging } } // Store log entry this.logs.push(entry); // Trim old logs if (this.logs.length > this.maxLogs) { this.logs = this.logs.slice(-this.maxLogs); } // Console output const timestamp = entry.timestamp.toISOString(); const prefix = this.getLogPrefix(timestamp, level); this.outputToConsole(level, prefix, message, data); } /** * Logs a message at the debug level. * Used for detailed troubleshooting information. * * @param message - The message to log * @param data - Optional additional context data */ debug(message, data) { this.log("debug", message, data); } /** * Logs a message at the info level. * Used for general information about system operation. * * @param message - The message to log * @param data - Optional additional context data */ info(message, data) { this.log("info", message, data); } /** * Logs a message at the warn level. * Used for potentially problematic situations that don't prevent operation. * * @param message - The message to log * @param data - Optional additional context data */ warn(message, data) { this.log("warn", message, data); } /** * Logs a message at the error level. * Used for critical issues that may cause failures. * * @param message - The message to log * @param data - Optional additional context data */ error(message, data) { this.log("error", message, data); } /** * Retrieves stored log entries, optionally filtered by log level. * Returns a copy of the log entries to prevent external modification. * * @param level - Optional log level to filter by * @returns Array of log entries, either all or filtered by level */ getLogs(level) { if (level) { return this.logs.filter((log) => log.level === level); } return [...this.logs]; } /** * Removes all stored log entries. * Useful for testing or when log history is no longer needed. */ clearLogs() { this.logs = []; } /** * Logs messages unconditionally using `console.log`. * * This method is part of a legacy simple logger interface for backward compatibility. * It bypasses the structured logging mechanism and should only be used when * unstructured, unconditional logging is required. * * Use with caution in production environments as it outputs to the console * regardless of the current log level or debug mode settings. * * Use cases: * - Critical system information that must always be visible * - Status messages during initialization before logging is fully configured * - Debugging in environments where normal logging might be suppressed * * @param args - The arguments to log. These are passed directly to `console.log`. */ always(...args) { console.log(...args); } /** * Displays tabular data unconditionally using `console.table`. * * Similar to the `always` method, this bypasses log level checks and * will display data regardless of current logging settings. * * Important differences from other logging methods: * - Does NOT store entries in the log history * - Does NOT use the structured logging format with timestamps and prefixes * - Outputs directly to console without additional formatting * * Particularly useful for: * - Displaying structured data in a readable format during debugging * - Showing configuration options and their current values * - Presenting comparison data between different system states * - Performance metrics and timing data * * @param data - The data to display in table format. Can be an array of objects or an object with key-value pairs. */ table(data) { console.table(data); } } // Export singleton instance to ensure consistent logging across the application const neuroLinkLogger = new NeuroLinkLogger(); /** * Helper function to process logger arguments with minimal overhead. * Handles variable argument patterns and ensures safe serialization of objects. * * This function: * 1. Extracts the first argument as the message * 2. Handles serialization of non-string first arguments * 3. Collects remaining arguments as additional data * 4. Passes the processed arguments to the actual logging method * * @param args - Array of arguments passed to the logger * @param logMethod - Function that will perform the actual logging */ function processLoggerArgs(args, logMethod) { if (args.length === 0) { return; } // Serialize the first argument robustly to handle complex objects const message = (() => { try { return typeof args[0] === "string" ? args[0] : JSON.stringify(args[0]); } catch { return "[Unserializable Object]"; } })(); const data = args.length === 2 ? args[1] : args.length > 2 ? args.slice(1) : undefined; logMethod(message, data); } /** * Main unified logger export that provides a simplified API for logging. * This is the primary interface that should be used by application code. * * Features: * - Convenient logging methods (debug, info, warn, error) * - Unconditional logging (always, table) * - Log level control and configuration * - Log history management * - Event emission for all log operations (when emitter is configured) */ export const logger = { debug: (...args) => { if (neuroLinkLogger.shouldLog("debug")) { processLoggerArgs(args, (message, data) => neuroLinkLogger.debug(message, data)); } }, info: (...args) => { if (neuroLinkLogger.shouldLog("info")) { processLoggerArgs(args, (message, data) => neuroLinkLogger.info(message, data)); } }, warn: (...args) => { if (neuroLinkLogger.shouldLog("warn")) { processLoggerArgs(args, (message, data) => neuroLinkLogger.warn(message, data)); } }, error: (...args) => { if (neuroLinkLogger.shouldLog("error")) { processLoggerArgs(args, (message, data) => neuroLinkLogger.error(message, data)); } }, always: (...args) => { neuroLinkLogger.always(...args); }, table: (data) => { neuroLinkLogger.table(data); }, // Expose log-level check for gating expensive operations shouldLog: (level) => neuroLinkLogger.shouldLog(level), // Expose structured logging methods setLogLevel: (level) => neuroLinkLogger.setLogLevel(level), getLogs: (level) => neuroLinkLogger.getLogs(level), clearLogs: () => neuroLinkLogger.clearLogs(), setEventEmitter: (emitter) => neuroLinkLogger.setEventEmitter(emitter), clearEventEmitter: () => neuroLinkLogger.clearEventEmitter(), }; /** * MCP compatibility exports - all use the same unified logger instance. * These exports maintain backward compatibility with code that expects * separate loggers for different MCP components, while actually using * the same underlying logger instance. */ export const mcpLogger = neuroLinkLogger; export const autoDiscoveryLogger = neuroLinkLogger; export const registryLogger = neuroLinkLogger; export const unifiedRegistryLogger = neuroLinkLogger; /** * Sets the global log level for all MCP-related logging. * This function provides a convenient way to adjust logging verbosity * for all MCP components at once. * * @param level - The log level to set ("debug", "info", "warn", or "error") */ export function setGlobalMCPLogLevel(level) { neuroLinkLogger.setLogLevel(level); } /** * Export LogLevel enum for runtime use. * Provides type-safe log level constants for use in application code. * * Example usage: * ``` * import { logger, LogLevels } from './logger'; // Import from your project's path * * // Using the LogLevels constants (recommended for type safety): * logger.setLogLevel(LogLevels.debug); * * // Or directly using string values: * logger.setLogLevel('debug'); * ``` */ export const LogLevels = { debug: "debug", info: "info", warn: "warn", error: "error", }; //# sourceMappingURL=logger.js.map