Skip to content

Commit 56d96f7

Browse files
authored
Merge pull request #3974 from elizaOS/v2-clear-logs
feat: add clear logs method and api
2 parents 5018736 + 186cdeb commit 56d96f7

File tree

4 files changed

+93
-5
lines changed

4 files changed

+93
-5
lines changed

packages/cli/src/server/api/index.ts

+29
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,35 @@ export function createApiRouter(
603603
router.get('/logs', logsHandler);
604604
router.post('/logs', logsHandler);
605605

606+
// Handler for clearing logs
607+
const logsClearHandler = (_req, res) => {
608+
try {
609+
// Access the underlying logger instance
610+
const destination = (logger as unknown)[Symbol.for('pino-destination')];
611+
612+
if (!destination?.clear) {
613+
return res.status(500).json({
614+
error: 'Logger clear method not available',
615+
message: 'The logger is not configured to clear logs',
616+
});
617+
}
618+
619+
// Clear the logs
620+
destination.clear();
621+
622+
logger.debug('Logs cleared via API endpoint');
623+
res.json({ status: 'success', message: 'Logs cleared successfully' });
624+
} catch (error) {
625+
res.status(500).json({
626+
error: 'Failed to clear logs',
627+
message: error instanceof Error ? error.message : 'Unknown error',
628+
});
629+
}
630+
};
631+
632+
// Add DELETE endpoint for clearing logs
633+
router.delete('/logs', logsClearHandler);
634+
606635
// Health check endpoints
607636
router.get('/health', (_req, res) => {
608637
logger.log({ apiRoute: '/health' }, 'Health check route hit');

packages/client/src/components/log-viewer.tsx

+26-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { useQuery } from '@tanstack/react-query';
22
import { format } from 'date-fns';
3-
import { useEffect, useRef, useState } from 'react';
3+
import { useEffect, useRef, useState, useCallback } from 'react';
44
import { useAgents } from '../hooks/use-query-hooks';
55
import { apiClient } from '../lib/api';
66
import PageTitle from './page-title';
77
import { ScrollArea } from './ui/scroll-area';
88
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
9+
import { useQueryClient } from '@tanstack/react-query';
10+
import { Button } from './ui/button';
911

1012
interface LogEntry {
1113
level: number;
@@ -60,9 +62,11 @@ export function LogViewer({ agentName, level, hideTitle }: LogViewerProps = {})
6062
const [selectedLevel, setSelectedLevel] = useState(level || 'all');
6163
const [selectedAgentName, setSelectedAgentName] = useState(agentName || 'all');
6264
const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
65+
const [isClearing, setIsClearing] = useState(false);
6366
const scrollAreaRef = useRef<HTMLDivElement>(null);
6467
const isUserScrolling = useRef(false);
6568
const lastLogId = useRef<string>('');
69+
const queryClient = useQueryClient();
6670

6771
const { data, error, isLoading } = useQuery<LogResponse>({
6872
queryKey: ['logs', selectedLevel, selectedAgentName],
@@ -78,7 +82,20 @@ export function LogViewer({ agentName, level, hideTitle }: LogViewerProps = {})
7882
const { data: agents } = useAgents();
7983
const agentNames = agents?.data?.agents?.map((agent) => agent.name) ?? [];
8084

81-
const scrollToBottom = () => {
85+
const handleClearLogs = async () => {
86+
try {
87+
setIsClearing(true);
88+
await apiClient.deleteLogs();
89+
// Invalidate the logs query to trigger a refetch
90+
queryClient.invalidateQueries({ queryKey: ['logs'] });
91+
} catch (error) {
92+
console.error('Failed to clear logs:', error);
93+
} finally {
94+
setIsClearing(false);
95+
}
96+
};
97+
98+
const scrollToBottom = useCallback(() => {
8299
if (!scrollAreaRef.current) return;
83100

84101
const scrollArea = scrollAreaRef.current;
@@ -89,7 +106,7 @@ export function LogViewer({ agentName, level, hideTitle }: LogViewerProps = {})
89106
top: scrollHeight - clientHeight,
90107
behavior: 'instant',
91108
});
92-
};
109+
}, []);
93110

94111
useEffect(() => {
95112
if (!data?.logs?.length) return;
@@ -101,7 +118,7 @@ export function LogViewer({ agentName, level, hideTitle }: LogViewerProps = {})
101118
setTimeout(scrollToBottom, 0);
102119
lastLogId.current = currentLastLogId;
103120
}
104-
}, [data?.logs, shouldAutoScroll]);
121+
}, [data?.logs, shouldAutoScroll, scrollToBottom]);
105122

106123
const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
107124
if (isUserScrolling.current) return;
@@ -125,7 +142,7 @@ export function LogViewer({ agentName, level, hideTitle }: LogViewerProps = {})
125142

126143
useEffect(() => {
127144
setTimeout(scrollToBottom, 100);
128-
}, [data?.logs]);
145+
}, [scrollToBottom]);
129146

130147
const getLevelName = (level: number) => {
131148
return LOG_LEVEL_NUMBERS[level as keyof typeof LOG_LEVEL_NUMBERS] || 'UNKNOWN';
@@ -164,6 +181,10 @@ export function LogViewer({ agentName, level, hideTitle }: LogViewerProps = {})
164181
<div className="mb-4 flex items-center justify-between">
165182
{!hideTitle && <PageTitle title={'System Logs'} />}
166183
<div className="flex items-center gap-4">
184+
<Button variant="destructive" size="sm" onClick={handleClearLogs} disabled={isClearing}>
185+
{isClearing ? 'Clearing...' : 'Clear Logs'}
186+
</Button>
187+
167188
{!shouldAutoScroll && (
168189
<button
169190
type="button"

packages/client/src/lib/api.ts

+8
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,14 @@ export const apiClient = {
369369
});
370370
},
371371

372+
// Method to clear logs
373+
deleteLogs: (): Promise<{ status: string; message: string }> => {
374+
return fetcher({
375+
url: '/logs',
376+
method: 'DELETE',
377+
});
378+
},
379+
372380
getAgentCompletion: (
373381
agentId: string,
374382
senderId: string,

packages/core/src/logger.ts

+30
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ class InMemoryDestination implements DestinationStream {
132132
recentLogs(): LogEntry[] {
133133
return this.logs;
134134
}
135+
136+
/**
137+
* Clears all logs from memory.
138+
*
139+
* @returns {void}
140+
*/
141+
clear(): void {
142+
this.logs = [];
143+
}
135144
}
136145

137146
const customLevels: Record<string, number> = {
@@ -197,6 +206,11 @@ const options = {
197206
// Create basic logger initially
198207
let logger = pino(options);
199208

209+
// Add type for logger with clear method
210+
interface LoggerWithClear extends pino.Logger {
211+
clear: () => void;
212+
}
213+
200214
// Enhance logger with custom destination in Node.js environment
201215
if (typeof process !== 'undefined') {
202216
// Create the destination with in-memory logging
@@ -247,6 +261,14 @@ if (typeof process !== 'undefined') {
247261
const destination = new InMemoryDestination(prettyStream);
248262
logger = pino(options, destination);
249263
(logger as unknown)[Symbol.for('pino-destination')] = destination;
264+
265+
// Add clear method to logger
266+
(logger as unknown as LoggerWithClear).clear = () => {
267+
const destination = (logger as unknown)[Symbol.for('pino-destination')];
268+
if (destination instanceof InMemoryDestination) {
269+
destination.clear();
270+
}
271+
};
250272
});
251273
}
252274
}
@@ -256,6 +278,14 @@ if (typeof process !== 'undefined') {
256278
const destination = new InMemoryDestination(stream);
257279
logger = pino(options, destination);
258280
(logger as unknown)[Symbol.for('pino-destination')] = destination;
281+
282+
// Add clear method to logger
283+
(logger as unknown as LoggerWithClear).clear = () => {
284+
const destination = (logger as unknown)[Symbol.for('pino-destination')];
285+
if (destination instanceof InMemoryDestination) {
286+
destination.clear();
287+
}
288+
};
259289
}
260290
}
261291

0 commit comments

Comments
 (0)