@@ -81,6 +81,10 @@ interface ExtendedLoggerOptions extends LoggerOptions {
81
81
environment ?: string // Environment prefix for log entries
82
82
}
83
83
84
+ interface NetworkError extends Error {
85
+ code ?: string
86
+ }
87
+
84
88
export class Logger {
85
89
private name : string
86
90
private fileLocks : Map < string , number > = new Map ( )
@@ -233,84 +237,130 @@ export class Logger {
233
237
// Use a custom operation tracking approach that allows us to cancel it
234
238
const operationPromise = ( async ( ) => {
235
239
let fd : number | undefined
236
- try {
237
- // Ensure storage/logs folder structure exists
240
+ let retries = 0
241
+ const maxRetries = 3
242
+ const backoffDelay = 1000 // Initial delay of 1 second
243
+
244
+ while ( retries < maxRetries ) {
238
245
try {
239
- // First check if directory exists to avoid unnecessary mkdir calls
246
+ // Ensure storage/logs folder structure exists
240
247
try {
241
- await access ( this . config . logDirectory , constants . F_OK | constants . W_OK )
248
+ // First check if directory exists to avoid unnecessary mkdir calls
249
+ try {
250
+ await access ( this . config . logDirectory , constants . F_OK | constants . W_OK )
251
+ }
252
+ catch ( err ) {
253
+ if ( err instanceof Error && 'code' in err ) {
254
+ // Handle specific error codes
255
+ if ( err . code === 'ENOENT' ) {
256
+ // Directory doesn't exist
257
+ await mkdir ( this . config . logDirectory , { recursive : true , mode : 0o755 } )
258
+ }
259
+ else if ( err . code === 'EACCES' ) {
260
+ throw new Error ( `No write permission for log directory: ${ this . config . logDirectory } ` )
261
+ }
262
+ else {
263
+ throw err
264
+ }
265
+ }
266
+ else {
267
+ throw err
268
+ }
269
+ }
242
270
}
243
- catch {
244
- // If directory doesn't exist or isn't writable, create it
245
- await mkdir ( this . config . logDirectory , { recursive : true , mode : 0o755 } )
271
+ catch ( err ) {
272
+ console . error ( 'Debug: [writeToFile] Failed to create log directory:' , err )
273
+ throw err
246
274
}
247
- }
248
- catch ( err ) {
249
- console . error ( 'Debug: [writeToFile] Failed to create log directory:' , err )
250
- throw err
251
- }
252
275
253
- // Check if operation was cancelled
254
- if ( cancelled )
255
- throw new Error ( 'Operation cancelled: Logger was destroyed' )
276
+ // Check if operation was cancelled
277
+ if ( cancelled )
278
+ throw new Error ( 'Operation cancelled: Logger was destroyed' )
256
279
257
- // Only encrypt if encryption is configured
258
- const dataToWrite = this . validateEncryptionConfig ( )
259
- ? ( await this . encrypt ( data ) ) . encrypted
260
- : Buffer . from ( data )
280
+ // Only encrypt if encryption is configured
281
+ const dataToWrite = this . validateEncryptionConfig ( )
282
+ ? ( await this . encrypt ( data ) ) . encrypted
283
+ : Buffer . from ( data )
261
284
262
- // Write to file with proper synchronization
263
- try {
264
- // Create file if it doesn't exist with proper permissions
265
- if ( ! existsSync ( this . currentLogFile ) ) {
266
- await writeFile ( this . currentLogFile , '' , { mode : 0o644 } )
267
- }
285
+ // Write to file with proper synchronization
286
+ try {
287
+ // Create file if it doesn't exist with proper permissions
288
+ if ( ! existsSync ( this . currentLogFile ) ) {
289
+ await writeFile ( this . currentLogFile , '' , { mode : 0o644 } )
290
+ }
268
291
269
- // Open file for appending with exclusive access
270
- fd = openSync ( this . currentLogFile , 'a' , 0o644 )
292
+ // Open file for appending with exclusive access
293
+ fd = openSync ( this . currentLogFile , 'a' , 0o644 )
271
294
272
- // Append the log entry
273
- writeFileSync ( fd , dataToWrite , { flag : 'a' } )
274
- // Force sync to ensure data is written to disk
275
- fsyncSync ( fd )
295
+ // Append the log entry
296
+ writeFileSync ( fd , dataToWrite , { flag : 'a' } )
297
+ // Force sync to ensure data is written to disk
298
+ fsyncSync ( fd )
276
299
277
- // Close the file descriptor before checking size
278
- if ( fd !== undefined ) {
279
- closeSync ( fd )
280
- fd = undefined
281
- }
300
+ // Close the file descriptor before checking size
301
+ if ( fd !== undefined ) {
302
+ closeSync ( fd )
303
+ fd = undefined
304
+ }
282
305
283
- // Verify file exists and has content
284
- const stats = await stat ( this . currentLogFile )
285
- if ( stats . size === 0 ) {
286
- // If file is empty, try writing directly
287
- await writeFile ( this . currentLogFile , dataToWrite , { flag : 'w' , mode : 0o644 } )
288
- // Verify again
289
- const retryStats = await stat ( this . currentLogFile )
290
- if ( retryStats . size === 0 ) {
291
- throw new Error ( 'File exists but is empty after retry write' )
306
+ // Verify file exists and has content
307
+ const stats = await stat ( this . currentLogFile )
308
+ if ( stats . size === 0 ) {
309
+ // If file is empty, try writing directly
310
+ await writeFile ( this . currentLogFile , dataToWrite , { flag : 'w' , mode : 0o644 } )
311
+ // Verify again
312
+ const retryStats = await stat ( this . currentLogFile )
313
+ if ( retryStats . size === 0 ) {
314
+ throw new Error ( 'File exists but is empty after retry write' )
315
+ }
292
316
}
317
+
318
+ // If we reach here, write was successful
319
+ return
293
320
}
294
- }
295
- catch ( err ) {
296
- console . error ( 'Debug: [writeToFile] Error writing to file:' , err )
297
- throw err
298
- }
299
- finally {
300
- // Always close the file descriptor if it was opened
301
- if ( fd !== undefined ) {
302
- try {
303
- closeSync ( fd )
321
+ catch ( err : any ) {
322
+ const error = err as NetworkError
323
+ if ( error . code && [ 'ENETDOWN' , 'ENETUNREACH' , 'ENOTFOUND' , 'ETIMEDOUT' ] . includes ( error . code ) ) {
324
+ if ( retries < maxRetries - 1 ) {
325
+ const errorMessage = typeof error . message === 'string' ? error . message : 'Unknown error'
326
+ console . error ( `Network error during write attempt ${ retries + 1 } /${ maxRetries } :` , errorMessage )
327
+ // Exponential backoff
328
+ const delay : number = backoffDelay * ( 2 ** retries )
329
+ await new Promise ( resolve => setTimeout ( resolve , delay ) )
330
+ retries ++
331
+ continue
332
+ }
304
333
}
305
- catch ( err ) {
306
- console . error ( 'Debug: [writeToFile] Error closing file descriptor:' , err )
334
+ // Handle file system errors
335
+ if ( [ 'ENOSPC' , 'EDQUOT' ] . includes ( error . code ) ) {
336
+ throw new Error ( `Disk quota exceeded or no space left on device: ${ error . message } ` )
337
+ }
338
+ console . error ( 'Debug: [writeToFile] Error writing to file:' , error )
339
+ throw error
340
+ }
341
+ finally {
342
+ // Always close the file descriptor if it was opened
343
+ if ( fd !== undefined ) {
344
+ try {
345
+ closeSync ( fd )
346
+ }
347
+ catch ( err ) {
348
+ console . error ( 'Debug: [writeToFile] Error closing file descriptor:' , err )
349
+ }
307
350
}
308
351
}
309
352
}
310
- }
311
- catch ( err ) {
312
- console . error ( 'Debug: [writeToFile] Error in writeToFile:' , err )
313
- throw err
353
+ catch ( err : any ) {
354
+ if ( retries === maxRetries - 1 ) {
355
+ const error = err as Error
356
+ const errorMessage = typeof error . message === 'string' ? error . message : 'Unknown error'
357
+ console . error ( 'Debug: [writeToFile] Max retries reached. Final error:' , errorMessage )
358
+ throw err
359
+ }
360
+ retries ++
361
+ const delay : number = backoffDelay * ( 2 ** ( retries - 1 ) )
362
+ await new Promise ( resolve => setTimeout ( resolve , delay ) )
363
+ }
314
364
}
315
365
} ) ( )
316
366
@@ -1208,7 +1258,7 @@ export class Logger {
1208
1258
1209
1259
return decrypted . toString ( 'utf8' )
1210
1260
}
1211
- catch ( err ) {
1261
+ catch ( err : any ) {
1212
1262
throw new Error ( `Decryption failed: ${ err instanceof Error ? err . message : String ( err ) } ` )
1213
1263
}
1214
1264
}
0 commit comments