@toolprint/hypertool-mcp
Version:
HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers
290 lines • 9.81 kB
JavaScript
/**
* Streamlined logging framework using @toolprint/mcp-logger 0.0.5 native features
*/
import { createLogger, createLoggerSync, } from "@toolprint/mcp-logger";
import { join } from "path";
import { homedir } from "os";
import { APP_TECHNICAL_NAME, BRAND_NAME } from "../config/appConfig.js";
export const DEFAULT_LOGGING_CONFIG = {
level: process.env.NODE_ENV === "development" ? "debug" : "info",
enableConsole: true,
enableFile: true,
serverName: APP_TECHNICAL_NAME,
format: "pretty",
};
/**
* Create logging configuration based on runtime options and environment
*/
export function createLoggingConfig(runtimeOptions) {
// Determine format from environment variable or default to pretty
const format = (process.env.LOG_FORMAT === "json" ? "json" : "pretty");
return {
...DEFAULT_LOGGING_CONFIG,
format,
// Override level if provided in runtime options
level: runtimeOptions?.logLevel ||
DEFAULT_LOGGING_CONFIG.level,
};
}
/**
* Get the log file path for a given server name
*/
function getLogFilePath(serverName) {
const logDir = join(homedir(), `.${BRAND_NAME.toLowerCase()}`, serverName, "logs");
return join(logDir, `${serverName}.log`);
}
/**
* Convert LoggingConfig to mcp-logger LoggerOptions with transport-aware mode
*/
function configToLoggerOptions(config, runtimeOptions) {
const transports = [];
// Determine ServerMode based on runtime transport
const mode = runtimeOptions?.transport === "stdio" ? "local" : "remote";
// Console/stderr transport - use stderr for stdio mode, console for http mode
if (config.enableConsole) {
if (mode === "local") {
// stdio mode: use stderr to avoid protocol corruption
transports.push({
type: "stderr",
enabled: true,
});
}
else {
// http mode: use console for visibility
transports.push({
type: "console",
enabled: true,
});
}
}
// File transport
if (config.enableFile) {
transports.push({
type: "file",
enabled: true,
options: {
path: getLogFilePath(config.serverName),
mkdir: true,
append: true,
},
});
}
return {
mode,
level: config.level,
format: config.format,
transports,
};
}
/**
* Logger class that wraps mcp-logger with our API compatibility layer
*/
export class Logger {
mcpLogger;
config;
runtimeOptions;
constructor(config = {}, runtimeOptions) {
this.config = { ...DEFAULT_LOGGING_CONFIG, ...config };
this.runtimeOptions = runtimeOptions;
// Start with sync logger for immediate availability, then upgrade to async
const options = configToLoggerOptions(this.config, this.runtimeOptions);
this.mcpLogger = createLoggerSync(options);
// Upgrade to async logger in background for full file transport functionality
this.initializeAsync().catch(() => {
// Continue with sync logger if async fails
});
}
/**
* Initialize with async logger for full functionality
* This enables file transport which only works with async logger in mcp-logger 0.0.6
*/
async initializeAsync() {
try {
const options = configToLoggerOptions(this.config, this.runtimeOptions);
this.mcpLogger = await createLogger(options);
}
catch (error) {
// Continue with sync logger if async initialization fails
// File transport may not work, but console/stderr will still function
// Log this through the sync logger (stderr/console will still work)
this.mcpLogger.warn("Failed to initialize async logger, file transport may not work", {
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
});
}
}
// Helper method to format context for log messages
formatContext(context) {
if (context === undefined || context === null) {
return undefined;
}
if (context instanceof Error) {
return {
error: {
name: context.name,
message: context.message,
stack: context.stack,
},
};
}
if (typeof context === "object") {
return context;
}
// For primitive types, wrap in a context object
return { context };
}
// Public logging methods
fatal(message, context) {
// mcp-logger doesn't have fatal, use error
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.error(`[FATAL] ${message}`, formattedContext);
}
else {
this.mcpLogger.error(`[FATAL] ${message}`);
}
}
error(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.error(message, formattedContext);
}
else {
this.mcpLogger.error(message);
}
}
warn(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.warn(message, formattedContext);
}
else {
this.mcpLogger.warn(message);
}
}
info(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.info(message, formattedContext);
}
else {
this.mcpLogger.info(message);
}
}
debug(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.debug(message, formattedContext);
}
else {
this.mcpLogger.debug(message);
}
}
trace(message, context) {
const formattedContext = this.formatContext(context);
if (formattedContext) {
this.mcpLogger.trace(message, formattedContext);
}
else {
this.mcpLogger.trace(message);
}
}
// Child logger creation using native mcp-logger functionality
child(bindings) {
const module = bindings.module;
const otherBindings = { ...bindings };
delete otherBindings.module;
// Create child logger with module context
// mcp-logger 0.0.7+ handles caching natively to prevent memory leaks
const childMcpLogger = this.mcpLogger.child({
context: {
module,
...otherBindings,
},
});
// Create new Logger instance wrapping the child
const childLogger = new Logger(this.config, this.runtimeOptions);
childLogger.mcpLogger = childMcpLogger;
return childLogger;
}
// Legacy API compatibility - access to underlying logger
get pino() {
return this.mcpLogger;
}
get mcp() {
return this.mcpLogger;
}
// Update configuration and reinitialize
updateConfig(config) {
this.config = { ...this.config, ...config };
const options = configToLoggerOptions(this.config, this.runtimeOptions);
this.mcpLogger = createLoggerSync(options);
// Try to upgrade to async logger in background
this.initializeAsync().catch(() => {
// Ignore errors, sync logger will continue to work
});
}
}
// Global logger management
let globalLogger = null;
let selectedLoggingConfig = null;
/**
* Get or create the global logger instance
*/
export function getLogger(config, runtimeOptions) {
if (config || runtimeOptions) {
const finalConfig = config || createLoggingConfig(runtimeOptions);
globalLogger = new Logger(finalConfig, runtimeOptions);
selectedLoggingConfig = finalConfig;
return globalLogger;
}
if (!globalLogger) {
globalLogger = new Logger();
}
return globalLogger;
}
/**
* Get the currently active logging configuration
*/
export function getActiveLoggingConfig() {
return selectedLoggingConfig;
}
/**
* Create a child logger with module context
* This maintains backward compatibility with the existing API
*/
export function createChildLogger(bindings) {
// If we don't have a global logger yet, create one with environment-aware config
if (!globalLogger) {
const config = createLoggingConfig();
// For early initialization, assume stdio transport (most common case)
// This will be overridden when the main logger is properly initialized
const runtimeOptions = {
transport: "stdio",
debug: false,
insecure: false,
};
globalLogger = new Logger(config, runtimeOptions);
selectedLoggingConfig = config;
}
return globalLogger.child(bindings);
}
/**
* Get diagnostic information about child loggers from the native mcp-logger cache
* Useful for debugging EventEmitter memory leaks
*/
export function getLoggerDiagnostics() {
const result = {
hasGlobalLogger: globalLogger !== null,
};
// Access native mcp-logger cache stats if available through public API
if (globalLogger?.mcp &&
typeof globalLogger.mcp.getChildLoggerCacheStats === "function") {
result.cacheStats = globalLogger.mcp.getChildLoggerCacheStats();
}
return result;
}
//# sourceMappingURL=logging-backup.js.map