@@ -3,7 +3,6 @@ import { ObjectId } from 'bson';
3
3
import { once } from 'events' ;
4
4
import { createWriteStream , promises as fs } from 'fs' ;
5
5
import { createGzip , constants as zlibConstants } from 'zlib' ;
6
- import { Heap } from 'heap-js' ;
7
6
import { MongoLogWriter } from './mongo-log-writer' ;
8
7
import { Writable } from 'stream' ;
9
8
@@ -40,9 +39,37 @@ export class MongoLogManager {
40
39
/** Clean up log files older than `retentionDays`. */
41
40
async cleanupOldLogFiles ( maxDurationMs = 5_000 ) : Promise < void > {
42
41
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
+
44
49
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
+ }
46
73
} catch {
47
74
return ;
48
75
}
@@ -51,32 +78,19 @@ export class MongoLogManager {
51
78
// Delete files older than N days
52
79
const deletionCutoffTimestamp =
53
80
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 ) ;
61
81
62
82
const storageSizeLimit = this . _options . retentionGB
63
83
? this . _options . retentionGB * 1024 * 1024 * 1024
64
84
: Infinity ;
65
- let usedStorageSize = this . _options . retentionGB ? 0 : - Infinity ;
66
85
67
- for await ( const dirent of dirHandle ) {
86
+ for await ( const { id , fullPath } of [ ... sortedLogFiles ] ) {
68
87
// Cap the overall time spent inside this function. Consider situations like
69
88
// a large number of machines using a shared network-mounted $HOME directory
70
89
// where lots and lots of log files end up and filesystem operations happen
71
90
// with network latency.
72
91
if ( Date . now ( ) - deletionStartTimestamp > maxDurationMs ) break ;
73
92
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 ;
78
93
const fileTimestamp = + new ObjectId ( id ) . getTimestamp ( ) ;
79
- const fullPath = path . join ( dir , dirent . name ) ;
80
94
let toDelete :
81
95
| {
82
96
fullPath : string ;
@@ -94,32 +108,13 @@ export class MongoLogManager {
94
108
fullPath,
95
109
} ;
96
110
} 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
-
116
111
const reachedMaxStorageSize = usedStorageSize > storageSizeLimit ;
117
112
const reachedMaxFileCount =
118
113
this . _options . maxLogFileCount &&
119
- leastRecentFileHeap . size ( ) > this . _options . maxLogFileCount ;
114
+ sortedLogFiles . length > this . _options . maxLogFileCount ;
120
115
121
116
if ( reachedMaxStorageSize || reachedMaxFileCount ) {
122
- toDelete = leastRecentFileHeap . pop ( ) ;
117
+ toDelete = sortedLogFiles . shift ( ) ;
123
118
}
124
119
}
125
120
@@ -132,8 +127,7 @@ export class MongoLogManager {
132
127
// eslint-disable-next-line @typescript-eslint/no-explicit-any
133
128
} catch ( err : any ) {
134
129
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 ) ;
137
131
}
138
132
}
139
133
}
0 commit comments