describe('LatencyTracker logging', () => { const info = jest.fn(); const warn = jest.fn(); const error = jest.fn(); const restoreEnv = () => { delete process.env.MCP_VERBOSE_LATENCY_LOGGING; delete process.env.SECOND_OPINION_VERBOSE_LOGGING; }; beforeEach(() => { jest.resetModules(); restoreEnv(); info.mockClear(); warn.mockClear(); error.mockClear(); }); afterEach(() => { restoreEnv(); }); test('emits structured stage and model logs with metrics snapshot', async () => { process.env.MCP_VERBOSE_LATENCY_LOGGING = 'true'; const { LatencyTracker, setLatencyTrackerLogger, setVerboseLatencyLogging, isVerboseLatencyLoggingEnabled } = await import('../LatencyTracker.js'); setLatencyTrackerLogger({ info, warn, error }); try { expect(isVerboseLatencyLoggingEnabled()).toBe(true); const tracker = new LatencyTracker('test_flow', { agent: 'test_agent' }); tracker.startStage('stage_one'); tracker.endStage('stage_one', { tokens: 42, cost: 0.12345678 }); tracker.recordStage('stage_two', 0, { reason: 'skipped' }); tracker.recordModelLatency('model-a', 150, 'success', { stage: 'stage_one', tokens: 100, cost: 0.000123 }); tracker.recordModelLatency('model-b', 275, 'error', { stage: 'stage_two', errorMessage: 'timeout', timeoutMs: 2000 }); expect(info).toHaveBeenCalledWith('Stage complete', expect.objectContaining({ flow: 'test_flow', stage: 'stage_one', tokens: 42, cost: 0.12345678 })); expect(info).toHaveBeenCalledWith('Stage recorded', expect.objectContaining({ flow: 'test_flow', stage: 'stage_two', reason: 'skipped' })); expect(info).toHaveBeenCalledWith('Model latency recorded', expect.objectContaining({ flow: 'test_flow', model: 'model-a', tokens: 100, cost: 0.000123 })); expect(warn).toHaveBeenCalledWith('Model latency recorded with error', expect.objectContaining({ flow: 'test_flow', model: 'model-b', error: 'timeout', timeoutMs: 2000 })); const snapshot = tracker.getSnapshot(); expect(snapshot.context).toMatchObject({ agent: 'test_agent' }); expect(snapshot.stages).toEqual(expect.arrayContaining([ expect.objectContaining({ name: 'stage_one', meta: expect.objectContaining({ tokens: 42, cost: 0.12345678 }) }) ])); expect(snapshot.models).toEqual(expect.arrayContaining([ expect.objectContaining({ model: 'model-a', tokens: 100, cost: 0.000123 }) ])); } finally { setLatencyTrackerLogger({ info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console) }); setVerboseLatencyLogging(true); } }); test('honors environment toggles for verbose logging', async () => { process.env.MCP_VERBOSE_LATENCY_LOGGING = 'false'; const { LatencyTracker, setLatencyTrackerLogger, isVerboseLatencyLoggingEnabled, setVerboseLatencyLogging } = await import('../LatencyTracker.js'); setLatencyTrackerLogger({ info, warn, error }); try { expect(isVerboseLatencyLoggingEnabled()).toBe(false); const tracker = new LatencyTracker('env_flow'); tracker.startStage('env_stage'); tracker.endStage('env_stage'); expect(info).not.toHaveBeenCalled(); expect(warn).not.toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); } finally { setLatencyTrackerLogger({ info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console) }); setVerboseLatencyLogging(true); } }); test('produces structured summary logs with totals and redacted context', async () => { const { LatencyTracker, setLatencyTrackerLogger, setVerboseLatencyLogging } = await import('../LatencyTracker.js'); setLatencyTrackerLogger({ info, warn, error }); try { const tracker = new LatencyTracker('summary_flow', { agent: 'summary_agent', sessionId: 'secret-session' }); tracker.recordStage('stage_short', 10, { reason: 'warmup' }); tracker.recordStage('stage_long', 25, { detail: 'main_work' }); tracker.recordModelLatency('model-fast', 50, 'success', { stage: 'stage_short', tokens: 10, cost: 0.001 }); tracker.recordModelLatency('model-slow', 175, 'error', { stage: 'stage_long', errorMessage: 'timeout', tokens: 42, cost: 0.123 }); const summary = tracker.logSummary('error', { message: 'Custom latency summary', totals: { totalTokens: 52, totalCost: 0.124, totalModels: 2 }, errorMessage: 'Failed during synthesis', includeContextKeys: ['agent'] }); expect(summary).not.toBeNull(); expect(info).toHaveBeenCalledWith('Custom latency summary', expect.objectContaining({ flow: 'summary_flow', status: 'error', totalStages: 2, totalModelCalls: 2, responseTotals: { totalTokens: 52, totalCost: 0.124, totalModels: 2 }, errorMessage: 'Failed during synthesis', longestStage: 'stage_long', longestStageDurationMs: 25, slowestModel: 'model-slow', slowestModelDurationMs: 175, slowestModelStage: 'stage_long', slowestModelStatus: 'error', slowestModelTokens: 42, slowestModelCost: 0.123, slowestModelError: 'timeout' })); expect(summary).toMatchObject({ context: { agent: 'summary_agent' }, longestStageMeta: { detail: 'main_work' } }); expect((summary as Record).context).not.toEqual(expect.objectContaining({ sessionId: 'secret-session' })); } finally { setLatencyTrackerLogger({ info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console) }); setVerboseLatencyLogging(true); } }); test('supports legacy verbose logging flag for backwards compatibility', async () => { process.env.SECOND_OPINION_VERBOSE_LOGGING = 'false'; const { LatencyTracker, setLatencyTrackerLogger, isVerboseLatencyLoggingEnabled, setVerboseLatencyLogging } = await import('../LatencyTracker.js'); setLatencyTrackerLogger({ info, warn, error }); try { expect(isVerboseLatencyLoggingEnabled()).toBe(false); const tracker = new LatencyTracker('legacy_flow'); tracker.startStage('legacy_stage'); tracker.endStage('legacy_stage'); expect(info).not.toHaveBeenCalled(); expect(warn).not.toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); } finally { setLatencyTrackerLogger({ info: console.info.bind(console), warn: console.warn.bind(console), error: console.error.bind(console) }); setVerboseLatencyLogging(true); } }); test('sanitizes branch names with slashes in metrics directory path', async () => { // Save original environment const originalBranch = process.env.GIT_BRANCH; const originalJest = process.env.JEST_WORKER_ID; // Set up test environment with a branch name containing slashes process.env.GIT_BRANCH = 'combined/pr547-549'; delete process.env.JEST_WORKER_ID; // Enable persist() to run try { const { LatencyTracker } = await import('../LatencyTracker.js'); const tracker = new LatencyTracker('test_sanitization_flow', { agent: 'test_agent' }); tracker.startStage('test_stage'); tracker.endStage('test_stage', { tokens: 10 }); // Attempt to persist metrics - this should work without ENOENT errors await tracker.persist('success', { testMeta: 'value' }); // Verify the metrics directory was created with sanitized branch name const fs = await import('fs/promises'); const path = await import('path'); const repoRoot = process.cwd(); const repoName = path.basename(repoRoot); // The branch name should be sanitized (slashes replaced with hyphens) const sanitizedBranch = 'combined-pr547-549'; const expectedDir = path.join('/tmp', repoName, sanitizedBranch); // Check that the sanitized directory exists const stat = await fs.stat(expectedDir); expect(stat.isDirectory()).toBe(true); // Verify that metrics file was created in the sanitized directory const files = await fs.readdir(expectedDir); const metricsFile = files.find(f => f.startsWith('test_sanitization_flow-')); expect(metricsFile).toBeDefined(); // Cleanup if (metricsFile) { await fs.unlink(path.join(expectedDir, metricsFile)); } } finally { // Restore environment if (originalBranch !== undefined) { process.env.GIT_BRANCH = originalBranch; } else { delete process.env.GIT_BRANCH; } if (originalJest !== undefined) { process.env.JEST_WORKER_ID = originalJest; } } }); });