Skip to main content
Glama

hypertool-mcp

logging.integration.test.tsโ€ข9.87 kB
/** * Integration tests for the enhanced logging system * These tests bypass the global mock to test the actual implementation */ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import fs from "fs"; import path from "path"; // Unmock the logging module for these tests to use real implementation vi.unmock("../logging.js"); import * as logging from "../logging.js"; import { getFeatureFlagService } from "../../config/featureFlagService.js"; describe("Enhanced Logging System - Integration Tests", () => { beforeEach(() => { // Clear environment variables delete process.env.HYPERTOOL_MCP_LOGGER_ENABLED; delete process.env.LOG_FORMAT; delete process.env.NODE_ENV; // Reset state logging.resetGlobalLogger(); getFeatureFlagService().reset(); }); afterEach(() => { // Clean up environment delete process.env.HYPERTOOL_MCP_LOGGER_ENABLED; delete process.env.LOG_FORMAT; delete process.env.NODE_ENV; // Reset state logging.resetGlobalLogger(); }); describe("Feature Flag Integration", () => { it("should use Pino implementation by default", () => { logging.resetGlobalLogger(); const logger = logging.getLogger(); const diagnostics = logging.getLoggerDiagnostics(); expect(diagnostics.hasGlobalLogger).toBe(true); expect(diagnostics.implementationType).toBe("pino"); // Test basic functionality expect(() => { logger.info("Test message"); logger.error("Test error"); logger.debug("Test debug"); }).not.toThrow(); }); it("should switch to mcp-logger when feature flag is enabled", () => { logging.resetGlobalLogger(); logging.forceSetMcpLoggerEnabled(true); const logger = logging.getLogger(); const diagnostics = logging.getLoggerDiagnostics(); expect(diagnostics.hasGlobalLogger).toBe(true); expect(diagnostics.implementationType).toBe("mcp-logger"); // Test basic functionality expect(() => { logger.info("Test message"); logger.error("Test error"); logger.debug("Test debug"); }).not.toThrow(); }); it("should respect environment variable", () => { process.env.HYPERTOOL_MCP_LOGGER_ENABLED = "true"; const logger = logging.getLogger(); const diagnostics = logging.getLoggerDiagnostics(); expect(diagnostics.implementationType).toBe("mcp-logger"); }); it("should handle falsy environment variable values", () => { const falsyValues = ["false", "0", "no", "off"]; for (const value of falsyValues) { process.env.HYPERTOOL_MCP_LOGGER_ENABLED = value; logging.resetGlobalLogger(); const logger = logging.getLogger(); const diagnostics = logging.getLoggerDiagnostics(); expect(diagnostics.implementationType).toBe("pino"); } }); }); describe("Memory Leak Prevention", () => { it("should prevent EventEmitter warnings with many child loggers", () => { logging.forceSetMcpLoggerEnabled(false); // Use Pino for predictable caching const originalWarning = console.warn; const warnings: string[] = []; console.warn = (message: string) => { warnings.push(message); originalWarning(message); }; try { // Create many child loggers for (let i = 0; i < 150; i++) { const child = logging.createChildLogger({ module: `TestModule${i}` }); child.info(`Test message ${i}`); } // Check that no EventEmitter warnings were emitted const emitterWarnings = warnings.filter( (w) => w.includes("MaxListenersExceededWarning") || w.includes("EventEmitter") || w.includes("memory leak") ); expect(emitterWarnings.length).toBe(0); // Verify cache is working and limited const diagnostics = logging.getLoggerDiagnostics(); expect(diagnostics.cacheStats).toBeDefined(); expect(diagnostics.cacheStats.childLoggerCount).toBeLessThanOrEqual( 100 ); } finally { console.warn = originalWarning; } }); it("should reuse cached child loggers for identical bindings", () => { logging.forceSetMcpLoggerEnabled(false); // Use Pino const child1 = logging.createChildLogger({ module: "SameModule" }); const child2 = logging.createChildLogger({ module: "SameModule" }); // Should be the same cached instance for Pino expect(child1).toBe(child2); const diagnostics = logging.getLoggerDiagnostics(); expect(diagnostics.cacheStats.childLoggerCount).toBe(1); }); }); describe("Backward Compatibility", () => { it("should maintain Logger class API", () => { logging.resetGlobalLogger(); const logger = logging.getLogger(); // Check all methods exist expect(typeof logger.fatal).toBe("function"); expect(typeof logger.error).toBe("function"); expect(typeof logger.warn).toBe("function"); expect(typeof logger.info).toBe("function"); expect(typeof logger.debug).toBe("function"); expect(typeof logger.trace).toBe("function"); expect(typeof logger.child).toBe("function"); expect(typeof logger.updateConfig).toBe("function"); // Check legacy properties expect(logger.pino).toBeDefined(); expect(logger.mcp).toBeDefined(); }); it("should handle Error objects in context", () => { logging.resetGlobalLogger(); const logger = logging.getLogger(); const testError = new Error("Test error"); testError.stack = "Test stack trace"; expect(() => { logger.error("Error occurred", testError); logger.warn("Warning with error", testError); logger.info("Info with error", testError); }).not.toThrow(); }); it("should handle various context types", () => { logging.resetGlobalLogger(); const logger = logging.getLogger(); expect(() => { logger.info("String context", "test string"); logger.info("Number context", 42); logger.info("Boolean context", true); logger.info("Object context", { key: "value", nested: { deep: true } }); logger.info("Null context", null); logger.info("Undefined context", undefined); }).not.toThrow(); }); }); describe("Configuration", () => { it("should support different log levels", () => { logging.resetGlobalLogger(); const config = { level: "debug" as const, enableConsole: true, enableFile: false, serverName: "test-server", format: "json" as const, }; const logger = logging.getLogger(config); expect(() => { logger.debug("Debug message"); logger.info("Info message"); logger.warn("Warning message"); logger.error("Error message"); }).not.toThrow(); }); it("should handle LOG_FORMAT environment variable", () => { process.env.LOG_FORMAT = "json"; logging.resetGlobalLogger(); const logger = logging.getLogger(); expect(() => { logger.info("JSON format test"); }).not.toThrow(); }); }); describe("Diagnostics", () => { it("should provide diagnostic information", () => { logging.resetGlobalLogger(); const logger = logging.getLogger(); const diagnostics = logging.getLoggerDiagnostics(); expect(diagnostics).toHaveProperty("hasGlobalLogger"); expect(diagnostics).toHaveProperty("implementationType"); expect(diagnostics.hasGlobalLogger).toBe(true); expect(["pino", "mcp-logger"]).toContain(diagnostics.implementationType); }); it("should provide cache statistics for Pino implementation", () => { logging.resetGlobalLogger(); logging.forceSetMcpLoggerEnabled(false); const logger = logging.getLogger(); logging.createChildLogger({ module: "TestModule" }); const diagnostics = logging.getLoggerDiagnostics(); expect(diagnostics.implementationType).toBe("pino"); expect(diagnostics.cacheStats).toBeDefined(); expect(diagnostics.cacheStats.childLoggerCount).toBeGreaterThan(0); }); }); describe("Implementation Switching", () => { it("should not leak memory when switching between implementations", () => { const originalWarning = console.warn; const warnings: string[] = []; console.warn = (message: string) => { warnings.push(message); originalWarning(message); }; try { // Create loggers with Pino logging.resetGlobalLogger(); logging.forceSetMcpLoggerEnabled(false); for (let i = 0; i < 25; i++) { const child = logging.createChildLogger({ module: `PinoModule${i}` }); child.info(`Pino message ${i}`); } // Switch to mcp-logger logging.forceSetMcpLoggerEnabled(true); for (let i = 0; i < 25; i++) { const child = logging.createChildLogger({ module: `McpModule${i}` }); child.info(`MCP message ${i}`); } // Switch back to Pino logging.forceSetMcpLoggerEnabled(false); for (let i = 0; i < 25; i++) { const child = logging.createChildLogger({ module: `PinoModule2${i}`, }); child.info(`Pino2 message ${i}`); } // No memory warnings should have been emitted const memoryWarnings = warnings.filter( (w) => w.toLowerCase().includes("memory") || w.toLowerCase().includes("leak") || w.includes("MaxListenersExceededWarning") ); expect(memoryWarnings.length).toBe(0); } finally { console.warn = originalWarning; } }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/toolprint/hypertool-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server