Files
yulunz c54a49383a feat(telemetry): persist date of the last tool call. (#2705)
The state file is now loaded into memory and stored within
ClearcutLogger. At each tool call it checks whether it's been more than
one day since the recorded value in the state file, and if so, log the
last tool active, and update the state file.

Regarding the state file's format, lastToolCall is not exactly the very
last one, but rather the last one that was **logged**. This is the same
with the existing lastActive timestamp (i.e. it's not the last timestamp
when the server is active, but rather the last timestamp when the active
status is logged). So here we keep the definitions consistent.

A caveat for future improvement: arguably the loadState() and
writeState() and everything in between should be a atomic unit (there
might be multiple mcp servers running), but I think it's probably good
enough for now. If we see weirdness we can make that happen.

I also added format guards to loadState() so we don't have to try and
catch everywhere else when de-serializing the string. We should actually
just deserialize in loadState() but let me do this in a follow-up CL.
2026-09-09 14:54:26 +00:00

184 lines
6.1 KiB
TypeScript

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import assert from 'node:assert';
import crypto from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import {describe, it, afterEach, beforeEach} from 'node:test';
import sinon from 'sinon';
import {ClearcutLogger} from '../../src/telemetry/ClearcutLogger.js';
import {ErrorCode} from '../../src/telemetry/errors.js';
import * as persistence from '../../src/telemetry/persistence.js';
import {WatchdogClient} from '../../src/telemetry/WatchdogClient.js';
describe('FilePersistence', () => {
let tmpDir: string;
let logServerErrorStub: sinon.SinonStub;
beforeEach(async () => {
tmpDir = path.join(
await fs.realpath(os.tmpdir()),
`telemetry-test-${crypto.randomUUID()}`,
);
await fs.mkdir(tmpDir, {recursive: true});
ClearcutLogger.resetForTesting();
const mockWatchdog = sinon.createStubInstance(WatchdogClient);
const logger = ClearcutLogger.initialize({
appVersion: '1.0.0',
persistence: new persistence.FilePersistence(tmpDir),
watchdogClient: mockWatchdog,
});
logServerErrorStub = sinon.stub(logger, 'logServerError');
});
afterEach(async () => {
sinon.restore();
ClearcutLogger.resetForTesting();
await fs.rm(tmpDir, {recursive: true, force: true});
});
describe('loadState', () => {
it('returns default state and does NOT log telemetry if file does not exist (ENOENT)', async () => {
const filePersistence = new persistence.FilePersistence(tmpDir);
const state = await filePersistence.loadState();
assert.deepStrictEqual(state, {});
sinon.assert.notCalled(logServerErrorStub);
});
it('returns default state and LOGS telemetry if load fails due to corruption', async () => {
const filePath = path.join(tmpDir, 'telemetry_state.json');
await fs.writeFile(filePath, 'not-valid-json', 'utf-8');
const filePersistence = new persistence.FilePersistence(tmpDir);
const state = await filePersistence.loadState();
assert.deepStrictEqual(state, {});
sinon.assert.calledOnceWithExactly(logServerErrorStub, {
errorCode: ErrorCode.ERROR_CODE_PERSISTENCE_FILE_READ_FAILED,
});
});
it('returns default state and LOGS telemetry if load fails during read stage', async () => {
const filePath = path.join(tmpDir, 'telemetry_state.json');
await fs.writeFile(filePath, '{"valid": "json"}', 'utf-8');
const readFileStub = sinon
.stub(fs, 'readFile')
.rejects(new Error('Synthetic read error'));
const filePersistence = new persistence.FilePersistence(tmpDir);
const state = await filePersistence.loadState();
assert.deepStrictEqual(state, {});
sinon.assert.calledOnceWithExactly(logServerErrorStub, {
errorCode: ErrorCode.ERROR_CODE_PERSISTENCE_FILE_READ_FAILED,
});
readFileStub.restore();
});
it('returns default state and LOGS telemetry if state file is empty', async () => {
const filePath = path.join(tmpDir, 'telemetry_state.json');
await fs.writeFile(filePath, '', 'utf-8');
const filePersistence = new persistence.FilePersistence(tmpDir);
const state = await filePersistence.loadState();
assert.deepStrictEqual(state, {});
sinon.assert.calledOnceWithExactly(logServerErrorStub, {
errorCode: ErrorCode.ERROR_CODE_PERSISTENCE_FILE_READ_FAILED,
});
});
it('returns default state if lastActive is invalid date string', async () => {
const filePath = path.join(tmpDir, 'telemetry_state.json');
await fs.writeFile(
filePath,
JSON.stringify({lastActive: 'invalid-date'}),
'utf-8',
);
const filePersistence = new persistence.FilePersistence(tmpDir);
const state = await filePersistence.loadState();
assert.deepStrictEqual(state, {});
sinon.assert.notCalled(logServerErrorStub);
});
it('returns default state if lastToolCall is invalid date string', async () => {
const filePath = path.join(tmpDir, 'telemetry_state.json');
await fs.writeFile(
filePath,
JSON.stringify({
lastActive: '2023-01-01T00:00:00.000Z',
lastToolCall: 'invalid-date',
}),
'utf-8',
);
const filePersistence = new persistence.FilePersistence(tmpDir);
const state = await filePersistence.loadState();
assert.deepStrictEqual(state, {});
sinon.assert.notCalled(logServerErrorStub);
});
it('returns stored state if file exists', async () => {
const expectedState = {
lastActive: '2023-01-01T00:00:00.000Z',
lastToolCall: '2023-01-01T12:00:00.000Z',
};
await fs.writeFile(
path.join(tmpDir, 'telemetry_state.json'),
JSON.stringify(expectedState),
);
const filePersistence = new persistence.FilePersistence(tmpDir);
const state = await filePersistence.loadState();
assert.deepStrictEqual(state, expectedState);
});
});
describe('saveState', () => {
it('saves state to file', async () => {
const state = {
lastActive: '2023-01-01T00:00:00.000Z',
lastToolCall: '2023-01-01T12:00:00.000Z',
};
const filePersistence = new persistence.FilePersistence(tmpDir);
await filePersistence.saveState(state);
const content = await fs.readFile(
path.join(tmpDir, 'telemetry_state.json'),
'utf-8',
);
assert.deepStrictEqual(JSON.parse(content), state);
sinon.assert.notCalled(logServerErrorStub);
});
it('logs telemetry when failing to save to file', async () => {
// Force error by replacing directory with a file, causing mkdir to fail.
const dirPath = path.join(tmpDir, 'blocked_dir');
await fs.writeFile(dirPath, 'i-am-a-file');
const filePersistence = new persistence.FilePersistence(dirPath);
const state = {
lastActive: '2023-01-01T00:00:00.000Z',
};
await filePersistence.saveState(state);
sinon.assert.calledOnceWithExactly(logServerErrorStub, {
errorCode: ErrorCode.ERROR_CODE_PERSISTENCE_FILE_SAVE_FAILED,
});
});
});
});