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