@@ -5,6 +5,16 @@ import { shell } from 'electron';
5
5
import { ConsoleLogger , LogLevel } from '@agent-infra/logger' ;
6
6
import { getOmegaDir } from '../mcp/client' ;
7
7
8
+ /**
9
+ * Maximum size of a log file (10MB)
10
+ */
11
+ export const MAX_LOG_FILE_SIZE = 10 * 1024 * 1024 ;
12
+
13
+ /**
14
+ * Maximum number of log files to keep
15
+ */
16
+ export const MAX_LOG_FILES = 5 ;
17
+
8
18
// Ensure log directory exists
9
19
const ensureLogDir = async ( ) : Promise < string > => {
10
20
const omegaDir = await getOmegaDir ( ) ;
@@ -24,6 +34,7 @@ const createLogFilePath = async (): Promise<string> => {
24
34
class FileLogger extends ConsoleLogger {
25
35
private logFilePath : string | null = null ;
26
36
private initPromise : Promise < void > | null = null ;
37
+ private currentLogFileSize = 0 ;
27
38
28
39
constructor ( prefix = '' , level : LogLevel = LogLevel . INFO ) {
29
40
super ( prefix , level ) ;
@@ -47,12 +58,19 @@ class FileLogger extends ConsoleLogger {
47
58
// Ensure log file exists
48
59
await fs . ensureFile ( this . logFilePath ) ;
49
60
61
+ // Get current file size if it exists
62
+ try {
63
+ const stats = await fs . stat ( this . logFilePath ) ;
64
+ this . currentLogFileSize = stats . size ;
65
+ } catch ( error ) {
66
+ this . currentLogFileSize = 0 ;
67
+ }
68
+
50
69
// Add startup marker
51
70
const timestamp = new Date ( ) . toISOString ( ) ;
52
- await fs . appendFile (
53
- this . logFilePath ,
54
- `\n\n--- Agent TARS started at ${ timestamp } ---\n\n` ,
55
- ) ;
71
+ const startupMessage = `\n\n--- Agent TARS started at ${ timestamp } ---\n\n` ;
72
+ await fs . appendFile ( this . logFilePath , startupMessage ) ;
73
+ this . currentLogFileSize += Buffer . byteLength ( startupMessage ) ;
56
74
} catch ( error ) {
57
75
console . error ( 'Failed to initialize log file:' , error ) ;
58
76
// Reset Promise to allow retry on next attempt
@@ -63,14 +81,84 @@ class FileLogger extends ConsoleLogger {
63
81
return this . initPromise ;
64
82
}
65
83
84
+ private async checkLogFileSize ( ) : Promise < void > {
85
+ if ( this . currentLogFileSize > MAX_LOG_FILE_SIZE && this . logFilePath ) {
86
+ // Create a new log file name with timestamp
87
+ const dir = path . dirname ( this . logFilePath ) ;
88
+ const baseFileName = path . basename ( this . logFilePath ) ;
89
+ const timestamp = new Date ( ) . toISOString ( ) . replace ( / [: .] / g, '-' ) ;
90
+ const rotatedFileName = `${ baseFileName } .${ timestamp } ` ;
91
+ const rotatedFilePath = path . join ( dir , rotatedFileName ) ;
92
+
93
+ try {
94
+ // Rename current log file to include timestamp
95
+ await fs . rename ( this . logFilePath , rotatedFilePath ) ;
96
+
97
+ // Clean up old log files if there are too many
98
+ await this . cleanupOldLogFiles ( dir ) ;
99
+
100
+ // Reset current log file
101
+ this . logFilePath = null ;
102
+ this . currentLogFileSize = 0 ;
103
+ await this . initLogFile ( ) ;
104
+ } catch ( error ) {
105
+ console . error ( 'Failed to rotate log file:' , error ) ;
106
+ }
107
+ }
108
+ }
109
+
110
+ private async cleanupOldLogFiles ( logDir : string ) : Promise < void > {
111
+ try {
112
+ // Get all log files
113
+ const files = await fs . readdir ( logDir ) ;
114
+ const logFiles = files . filter (
115
+ ( file ) => file . startsWith ( 'agent-tars-' ) && file . endsWith ( '.log' ) ,
116
+ ) ;
117
+
118
+ // If we have more than MAX_LOG_FILES, delete the oldest ones
119
+ if ( logFiles . length > MAX_LOG_FILES ) {
120
+ // Sort files by creation time (oldest first)
121
+ const fileStats = await Promise . all (
122
+ logFiles . map ( async ( file ) => {
123
+ const filePath = path . join ( logDir , file ) ;
124
+ const stats = await fs . stat ( filePath ) ;
125
+ return { file, filePath, ctime : stats . ctime } ;
126
+ } ) ,
127
+ ) ;
128
+
129
+ fileStats . sort ( ( a , b ) => a . ctime . getTime ( ) - b . ctime . getTime ( ) ) ;
130
+
131
+ // Delete oldest files
132
+ const filesToDelete = fileStats . slice (
133
+ 0 ,
134
+ fileStats . length - MAX_LOG_FILES ,
135
+ ) ;
136
+ for ( const fileInfo of filesToDelete ) {
137
+ await fs . unlink ( fileInfo . filePath ) ;
138
+ }
139
+ }
140
+ } catch ( error ) {
141
+ console . error ( 'Failed to clean up old log files:' , error ) ;
142
+ }
143
+ }
144
+
66
145
private async writeToFile ( message : string ) {
67
146
// Ensure log file is initialized
68
147
await this . initLogFile ( ) ;
69
148
70
149
if ( this . logFilePath ) {
71
150
try {
72
151
const timestamp = new Date ( ) . toISOString ( ) ;
73
- await fs . appendFile ( this . logFilePath , `[${ timestamp } ] ${ message } \n` ) ;
152
+ const logEntry = `[${ timestamp } ] ${ message } \n` ;
153
+ const logSize = Buffer . byteLength ( logEntry ) ;
154
+
155
+ // Check if adding this log would exceed the size limit
156
+ if ( this . currentLogFileSize + logSize > MAX_LOG_FILE_SIZE ) {
157
+ await this . checkLogFileSize ( ) ;
158
+ }
159
+
160
+ await fs . appendFile ( this . logFilePath , logEntry ) ;
161
+ this . currentLogFileSize += logSize ;
74
162
} catch ( error ) {
75
163
console . error ( 'Failed to write to log file:' , error ) ;
76
164
}
0 commit comments