fix(logging): address review feedback on cleanup and log reader

This commit is contained in:
Tam Nhu Tran committed 2026-04-08 15:57:15 -04:00
1 parent 382aa04ca0
commit 42481c0bb9
7 files changed
+410 -34

No files matched your search

+6 -9
View File
@@ -1,7 +1,7 @@
/**
* Cleanup Command Handler
*
* Removes old CLIProxy logs to free up disk space.
* Removes old CCS and CLIProxy logs to free up disk space.
* Supports both main logs and error request logs with age-based filtering.
* Logs can accumulate to several GB without user awareness.
*/
@@ -32,23 +32,20 @@ function formatBytes(bytes: number): string {
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
}
/** Calculate total size of a directory */
/** Calculate total size of regular top-level files in a directory */
function getDirSize(dirPath: string): number {
if (!fs.existsSync(dirPath)) return 0;
let totalSize = 0;
const files = fs.readdirSync(dirPath);
const entries = fs.readdirSync(dirPath);
for (const file of files) {
const filePath = path.join(dirPath, file);
for (const entry of entries) {
const filePath = path.join(dirPath, entry);
try {
const stats = fs.lstatSync(filePath); // Use lstat to detect symlinks
const stats = fs.lstatSync(filePath);
if (stats.isFile() && !stats.isSymbolicLink()) {
totalSize += stats.size;
} else if (stats.isDirectory() && !stats.isSymbolicLink()) {
totalSize += getDirSize(filePath);
}
// Skip symlinks for safety
} catch {
// File may have been deleted between readdir and stat - skip
}
+1 -1
View File
@@ -137,7 +137,7 @@ export const ROOT_COMMAND_CATALOG: readonly RootCommandEntry[] = [
},
{
name: 'cleanup',
summary: 'Remove old CLIProxy logs',
summary: 'Remove old CCS and CLIProxy logs',
group: 'operations',
aliases: ['--cleanup'],
visibility: 'public',
+59 -20
View File
@@ -8,6 +8,15 @@ import {
type ReadLogEntriesOptions,
} from './log-types';
type CurrentLogCache = {
entries: LogEntry[];
mtimeNs: bigint;
path: string;
size: bigint;
} | null;
let currentLogCache: CurrentLogCache = null;
function parseLogLine(line: string): LogEntry | null {
try {
return JSON.parse(line) as LogEntry;
@@ -19,16 +28,62 @@ function parseLogLine(line: string): LogEntry | null {
function readCurrentFileEntries(): LogEntry[] {
const currentLogPath = getCurrentLogPath();
if (!fs.existsSync(currentLogPath)) {
currentLogCache = null;
return [];
}
return fs
const stats = fs.statSync(currentLogPath, { bigint: true });
if (
currentLogCache &&
currentLogCache.path === currentLogPath &&
currentLogCache.mtimeNs === stats.mtimeNs &&
currentLogCache.size === stats.size
) {
return [...currentLogCache.entries];
}
const entries = fs
.readFileSync(currentLogPath, 'utf8')
.split('\n')
.map((line) => line.trim())
.filter(Boolean)
.map(parseLogLine)
.filter((entry): entry is LogEntry => entry !== null);
currentLogCache = {
entries,
mtimeNs: stats.mtimeNs,
path: currentLogPath,
size: stats.size,
};
return [...entries];
}
function matchesLogQuery(entry: LogEntry, options: ReadLogEntriesOptions): boolean {
if (options.source && entry.source !== options.source) {
return false;
}
if (options.level && entry.level !== options.level) {
return false;
}
if (!options.search) {
return true;
}
const search = options.search.toLowerCase();
return (
entry.message.toLowerCase().includes(search) ||
entry.event.toLowerCase().includes(search) ||
entry.source.toLowerCase().includes(search) ||
String(entry.processId).toLowerCase().includes(search) ||
entry.runId.toLowerCase().includes(search) ||
JSON.stringify(entry.context || {})
.toLowerCase()
.includes(search)
);
}
function dedupeEntries(entries: LogEntry[]): LogEntry[] {
@@ -40,28 +95,12 @@ function dedupeEntries(entries: LogEntry[]): LogEntry[] {
}
export function readLogEntries(options: ReadLogEntriesOptions = {}): LogEntry[] {
const limit = options.limit ?? 200;
const entries = dedupeEntries([...readCurrentFileEntries(), ...getRecentLogEntries()])
.filter((entry) => (options.source ? entry.source === options.source : true))
.filter((entry) => (options.level ? entry.level === options.level : true))
.filter((entry) => {
if (!options.search) {
return true;
}
const search = options.search.toLowerCase();
return (
entry.message.toLowerCase().includes(search) ||
entry.event.toLowerCase().includes(search) ||
entry.source.toLowerCase().includes(search) ||
String(entry.processId).toLowerCase().includes(search) ||
entry.runId.toLowerCase().includes(search) ||
JSON.stringify(entry.context || {})
.toLowerCase()
.includes(search)
);
})
.filter((entry) => matchesLogQuery(entry, options))
.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
return entries.slice(0, options.limit ?? 200);
return entries.slice(0, limit);
}
export function readLogSourceSummaries(): LogSourceSummary[] {