-
Notifications
You must be signed in to change notification settings - Fork 576
/
rotating_file_handler.ts
126 lines (113 loc) · 4.56 KB
/
rotating_file_handler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import type { LevelName } from "./levels.ts";
import { existsSync } from "../fs/exists.ts";
import { FileHandler, type FileHandlerOptions } from "./file_handler.ts";
interface RotatingFileHandlerOptions extends FileHandlerOptions {
maxBytes: number;
maxBackupCount: number;
}
/**
* This handler extends the functionality of the {@linkcode FileHandler} by
* "rotating" the log file when it reaches a certain size. `maxBytes` specifies
* the maximum size in bytes that the log file can grow to before rolling over
* to a new one. If the size of the new log message plus the current log file
* size exceeds `maxBytes` then a roll-over is triggered. When a roll-over
* occurs, before the log message is written, the log file is renamed and
* appended with `.1`. If a `.1` version already existed, it would have been
* renamed `.2` first and so on. The maximum number of log files to keep is
* specified by `maxBackupCount`. After the renames are complete the log message
* is written to the original, now blank, file.
*
* Example: Given `log.txt`, `log.txt.1`, `log.txt.2` and `log.txt.3`, a
* `maxBackupCount` of 3 and a new log message which would cause `log.txt` to
* exceed `maxBytes`, then `log.txt.2` would be renamed to `log.txt.3` (thereby
* discarding the original contents of `log.txt.3` since 3 is the maximum number
* of backups to keep), `log.txt.1` would be renamed to `log.txt.2`, `log.txt`
* would be renamed to `log.txt.1` and finally `log.txt` would be created from
* scratch where the new log message would be written.
*
* This handler uses a buffer for writing log messages to file. Logs can be
* manually flushed with `fileHandler.flush()`. Log messages with a log level
* greater than ERROR are immediately flushed. Logs are also flushed on process
* completion.
*
* Additional notes on `mode` as described above:
*
* - `'a'` Default mode. As above, this will pick up where the logs left off in
* rotation, or create a new log file if it doesn't exist.
* - `'w'` in addition to starting with a clean `filename`, this mode will also
* cause any existing backups (up to `maxBackupCount`) to be deleted on setup
* giving a fully clean slate.
* - `'x'` requires that neither `filename`, nor any backups (up to
* `maxBackupCount`), exist before setup.
*
* This handler requires both `--allow-read` and `--allow-write` permissions on
* the log files.
*/
export class RotatingFileHandler extends FileHandler {
#maxBytes: number;
#maxBackupCount: number;
#currentFileSize = 0;
constructor(levelName: LevelName, options: RotatingFileHandlerOptions) {
super(levelName, options);
this.#maxBytes = options.maxBytes;
this.#maxBackupCount = options.maxBackupCount;
}
override setup() {
if (this.#maxBytes < 1) {
this.destroy();
throw new Error("maxBytes cannot be less than 1");
}
if (this.#maxBackupCount < 1) {
this.destroy();
throw new Error("maxBackupCount cannot be less than 1");
}
super.setup();
if (this._mode === "w") {
// Remove old backups too as it doesn't make sense to start with a clean
// log file, but old backups
for (let i = 1; i <= this.#maxBackupCount; i++) {
try {
Deno.removeSync(this._filename + "." + i);
} catch (error) {
if (!(error instanceof Deno.errors.NotFound)) {
throw error;
}
}
}
} else if (this._mode === "x") {
// Throw if any backups also exist
for (let i = 1; i <= this.#maxBackupCount; i++) {
if (existsSync(this._filename + "." + i)) {
this.destroy();
throw new Deno.errors.AlreadyExists(
"Backup log file " + this._filename + "." + i + " already exists",
);
}
}
} else {
this.#currentFileSize = (Deno.statSync(this._filename)).size;
}
}
override log(msg: string) {
const msgByteLength = this._encoder.encode(msg).byteLength + 1;
if (this.#currentFileSize + msgByteLength > this.#maxBytes) {
this.rotateLogFiles();
this.#currentFileSize = 0;
}
super.log(msg);
this.#currentFileSize += msgByteLength;
}
rotateLogFiles() {
this.flush();
this._file!.close();
for (let i = this.#maxBackupCount - 1; i >= 0; i--) {
const source = this._filename + (i === 0 ? "" : "." + i);
const dest = this._filename + "." + (i + 1);
if (existsSync(source)) {
Deno.renameSync(source, dest);
}
}
this._file = Deno.openSync(this._filename, this._openOptions);
}
}