@@ -3,7 +3,6 @@ import { ObjectId } from 'bson';
33import { once } from 'events' ;
44import { createWriteStream , promises as fs } from 'fs' ;
55import { createGzip , constants as zlibConstants } from 'zlib' ;
6- import { Heap } from 'heap-js' ;
76import { MongoLogWriter } from './mongo-log-writer' ;
87import { Writable } from 'stream' ;
98
@@ -40,9 +39,37 @@ export class MongoLogManager {
4039 /** Clean up log files older than `retentionDays`. */
4140 async cleanupOldLogFiles ( maxDurationMs = 5_000 ) : Promise < void > {
4241 const dir = this . _options . directory ;
43- let dirHandle ;
42+ const sortedLogFiles : {
43+ fullPath : string ;
44+ id : string ;
45+ size ?: number ;
46+ } [ ] = [ ] ;
47+ let usedStorageSize = this . _options . retentionGB ? 0 : - Infinity ;
48+
4449 try {
45- dirHandle = await fs . opendir ( dir ) ;
50+ const files = await fs . readdir ( dir , { withFileTypes : true } ) ;
51+ for ( const file of files ) {
52+ const { id } =
53+ / ^ (?< id > [ a - f 0 - 9 ] { 24 } ) _ l o g ( \. g z ) ? $ / i. exec ( file . name ) ?. groups ?? { } ;
54+
55+ if ( ! file . isFile ( ) || ! id ) {
56+ continue ;
57+ }
58+
59+ const fullPath = path . join ( dir , file . name ) ;
60+ let size : number | undefined ;
61+ if ( this . _options . retentionGB ) {
62+ try {
63+ size = ( await fs . stat ( fullPath ) ) . size ;
64+ usedStorageSize += size ;
65+ } catch ( err ) {
66+ this . _options . onerror ( err as Error , fullPath ) ;
67+ continue ;
68+ }
69+ }
70+
71+ sortedLogFiles . push ( { fullPath, id, size } ) ;
72+ }
4673 } catch {
4774 return ;
4875 }
@@ -51,32 +78,19 @@ export class MongoLogManager {
5178 // Delete files older than N days
5279 const deletionCutoffTimestamp =
5380 deletionStartTimestamp - this . _options . retentionDays * 86400 * 1000 ;
54- // Store the known set of least recent files in a heap in order to be able to
55- // delete all but the most recent N files.
56- const leastRecentFileHeap = new Heap < {
57- fileTimestamp : number ;
58- fullPath : string ;
59- fileSize ?: number ;
60- } > ( ( a , b ) => a . fileTimestamp - b . fileTimestamp ) ;
6181
6282 const storageSizeLimit = this . _options . retentionGB
6383 ? this . _options . retentionGB * 1024 * 1024 * 1024
6484 : Infinity ;
65- let usedStorageSize = this . _options . retentionGB ? 0 : - Infinity ;
6685
67- for await ( const dirent of dirHandle ) {
86+ for await ( const { id , fullPath } of [ ... sortedLogFiles ] ) {
6887 // Cap the overall time spent inside this function. Consider situations like
6988 // a large number of machines using a shared network-mounted $HOME directory
7089 // where lots and lots of log files end up and filesystem operations happen
7190 // with network latency.
7291 if ( Date . now ( ) - deletionStartTimestamp > maxDurationMs ) break ;
7392
74- if ( ! dirent . isFile ( ) ) continue ;
75- const { id } =
76- / ^ (?< id > [ a - f 0 - 9 ] { 24 } ) _ l o g ( \. g z ) ? $ / i. exec ( dirent . name ) ?. groups ?? { } ;
77- if ( ! id ) continue ;
7893 const fileTimestamp = + new ObjectId ( id ) . getTimestamp ( ) ;
79- const fullPath = path . join ( dir , dirent . name ) ;
8094 let toDelete :
8195 | {
8296 fullPath : string ;
@@ -94,32 +108,13 @@ export class MongoLogManager {
94108 fullPath,
95109 } ;
96110 } else if ( this . _options . retentionGB || this . _options . maxLogFileCount ) {
97- let fileSize : number | undefined ;
98-
99- if ( this . _options . retentionGB ) {
100- try {
101- fileSize = ( await fs . stat ( fullPath ) ) . size ;
102- if ( this . _options . retentionGB ) {
103- usedStorageSize += fileSize ;
104- }
105- } catch ( err ) {
106- this . _options . onerror ( err as Error , fullPath ) ;
107- }
108- }
109-
110- leastRecentFileHeap . push ( {
111- fullPath,
112- fileTimestamp,
113- fileSize,
114- } ) ;
115-
116111 const reachedMaxStorageSize = usedStorageSize > storageSizeLimit ;
117112 const reachedMaxFileCount =
118113 this . _options . maxLogFileCount &&
119- leastRecentFileHeap . size ( ) > this . _options . maxLogFileCount ;
114+ sortedLogFiles . length > this . _options . maxLogFileCount ;
120115
121116 if ( reachedMaxStorageSize || reachedMaxFileCount ) {
122- toDelete = leastRecentFileHeap . pop ( ) ;
117+ toDelete = sortedLogFiles . shift ( ) ;
123118 }
124119 }
125120
@@ -132,8 +127,7 @@ export class MongoLogManager {
132127 // eslint-disable-next-line @typescript-eslint/no-explicit-any
133128 } catch ( err : any ) {
134129 if ( err ?. code !== 'ENOENT' ) {
135- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
136- this . _options . onerror ( err , fullPath ) ;
130+ this . _options . onerror ( err as Error , fullPath ) ;
137131 }
138132 }
139133 }
0 commit comments