@logtape/file 1.0.0-dev.236 → 1.0.0-dev.240
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist/filesink.base.cjs +129 -51
- package/dist/filesink.base.d.cts +51 -3
- package/dist/filesink.base.d.cts.map +1 -1
- package/dist/filesink.base.d.ts +51 -3
- package/dist/filesink.base.d.ts.map +1 -1
- package/dist/filesink.base.js +129 -51
- package/dist/filesink.base.js.map +1 -1
- package/dist/filesink.deno.cjs +20 -23
- package/dist/filesink.deno.d.cts +17 -4
- package/dist/filesink.deno.d.cts.map +1 -1
- package/dist/filesink.deno.d.ts +17 -4
- package/dist/filesink.deno.d.ts.map +1 -1
- package/dist/filesink.deno.js +20 -24
- package/dist/filesink.deno.js.map +1 -1
- package/dist/filesink.node.cjs +17 -23
- package/dist/filesink.node.d.cts +17 -4
- package/dist/filesink.node.d.cts.map +1 -1
- package/dist/filesink.node.d.ts +17 -4
- package/dist/filesink.node.d.ts.map +1 -1
- package/dist/filesink.node.js +17 -24
- package/dist/filesink.node.js.map +1 -1
- package/filesink.base.ts +250 -30
- package/filesink.deno.ts +43 -4
- package/filesink.jsr.ts +32 -7
- package/filesink.node.ts +40 -4
- package/filesink.test.ts +120 -0
- package/package.json +3 -2
- package/tsdown.config.ts +1 -1
package/deno.json
CHANGED
package/dist/filesink.base.cjs
CHANGED
|
@@ -2,15 +2,6 @@ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
|
2
2
|
const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape"));
|
|
3
3
|
|
|
4
4
|
//#region filesink.base.ts
|
|
5
|
-
/**
|
|
6
|
-
* Get a platform-independent file sink.
|
|
7
|
-
*
|
|
8
|
-
* @typeParam TFile The type of the file descriptor.
|
|
9
|
-
* @param path A path to the file to write to.
|
|
10
|
-
* @param options The options for the sink and the file driver.
|
|
11
|
-
* @returns A sink that writes to the file. The sink is also a disposable
|
|
12
|
-
* object that closes the file when disposed.
|
|
13
|
-
*/
|
|
14
5
|
function getBaseFileSink(path, options) {
|
|
15
6
|
const formatter = options.formatter ?? __logtape_logtape.defaultTextFormatter;
|
|
16
7
|
const encoder = options.encoder ?? new TextEncoder();
|
|
@@ -19,43 +10,79 @@ function getBaseFileSink(path, options) {
|
|
|
19
10
|
let fd = options.lazy ? null : options.openSync(path);
|
|
20
11
|
let buffer = "";
|
|
21
12
|
let lastFlushTimestamp = Date.now();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
if (!options.nonBlocking) {
|
|
14
|
+
function flushBuffer$1() {
|
|
15
|
+
if (fd == null) return;
|
|
16
|
+
if (buffer.length > 0) {
|
|
17
|
+
options.writeSync(fd, encoder.encode(buffer));
|
|
18
|
+
buffer = "";
|
|
19
|
+
options.flushSync(fd);
|
|
20
|
+
lastFlushTimestamp = Date.now();
|
|
21
|
+
}
|
|
29
22
|
}
|
|
23
|
+
const sink = (record) => {
|
|
24
|
+
if (fd == null) fd = options.openSync(path);
|
|
25
|
+
buffer += formatter(record);
|
|
26
|
+
const shouldFlushBySize = buffer.length >= bufferSize;
|
|
27
|
+
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
|
|
28
|
+
if (shouldFlushBySize || shouldFlushByTime) flushBuffer$1();
|
|
29
|
+
};
|
|
30
|
+
sink[Symbol.dispose] = () => {
|
|
31
|
+
if (fd !== null) {
|
|
32
|
+
flushBuffer$1();
|
|
33
|
+
options.closeSync(fd);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return sink;
|
|
37
|
+
}
|
|
38
|
+
const asyncOptions = options;
|
|
39
|
+
let disposed = false;
|
|
40
|
+
let activeFlush = null;
|
|
41
|
+
let flushTimer = null;
|
|
42
|
+
async function flushBuffer() {
|
|
43
|
+
if (fd == null || buffer.length === 0) return;
|
|
44
|
+
const data = buffer;
|
|
45
|
+
buffer = "";
|
|
46
|
+
try {
|
|
47
|
+
asyncOptions.writeSync(fd, encoder.encode(data));
|
|
48
|
+
await asyncOptions.flush(fd);
|
|
49
|
+
lastFlushTimestamp = Date.now();
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
52
|
+
function scheduleFlush() {
|
|
53
|
+
if (activeFlush || disposed) return;
|
|
54
|
+
activeFlush = flushBuffer().finally(() => {
|
|
55
|
+
activeFlush = null;
|
|
56
|
+
});
|
|
30
57
|
}
|
|
31
|
-
|
|
32
|
-
if (
|
|
58
|
+
function startFlushTimer() {
|
|
59
|
+
if (flushTimer !== null || disposed) return;
|
|
60
|
+
flushTimer = setInterval(() => {
|
|
61
|
+
scheduleFlush();
|
|
62
|
+
}, flushInterval);
|
|
63
|
+
}
|
|
64
|
+
const nonBlockingSink = (record) => {
|
|
65
|
+
if (disposed) return;
|
|
66
|
+
if (fd == null) fd = asyncOptions.openSync(path);
|
|
33
67
|
buffer += formatter(record);
|
|
34
68
|
const shouldFlushBySize = buffer.length >= bufferSize;
|
|
35
69
|
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
|
|
36
|
-
if (shouldFlushBySize || shouldFlushByTime)
|
|
70
|
+
if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
|
|
71
|
+
else if (flushTimer === null && flushInterval > 0) startFlushTimer();
|
|
37
72
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
73
|
+
nonBlockingSink[Symbol.asyncDispose] = async () => {
|
|
74
|
+
disposed = true;
|
|
75
|
+
if (flushTimer !== null) {
|
|
76
|
+
clearInterval(flushTimer);
|
|
77
|
+
flushTimer = null;
|
|
42
78
|
}
|
|
79
|
+
await flushBuffer();
|
|
80
|
+
if (fd !== null) try {
|
|
81
|
+
await asyncOptions.close(fd);
|
|
82
|
+
} catch {}
|
|
43
83
|
};
|
|
44
|
-
return
|
|
84
|
+
return nonBlockingSink;
|
|
45
85
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Get a platform-independent rotating file sink.
|
|
48
|
-
*
|
|
49
|
-
* This sink writes log records to a file, and rotates the file when it reaches
|
|
50
|
-
* the `maxSize`. The rotated files are named with the original file name
|
|
51
|
-
* followed by a dot and a number, starting from 1. The number is incremented
|
|
52
|
-
* for each rotation, and the maximum number of files to keep is `maxFiles`.
|
|
53
|
-
*
|
|
54
|
-
* @param path A path to the file to write to.
|
|
55
|
-
* @param options The options for the sink and the file driver.
|
|
56
|
-
* @returns A sink that writes to the file. The sink is also a disposable
|
|
57
|
-
* object that closes the file when disposed.
|
|
58
|
-
*/
|
|
59
86
|
function getBaseRotatingFileSink(path, options) {
|
|
60
87
|
const formatter = options.formatter ?? __logtape_logtape.defaultTextFormatter;
|
|
61
88
|
const encoder = options.encoder ?? new TextEncoder();
|
|
@@ -70,6 +97,7 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
70
97
|
} catch {}
|
|
71
98
|
let fd = options.openSync(path);
|
|
72
99
|
let lastFlushTimestamp = Date.now();
|
|
100
|
+
let buffer = "";
|
|
73
101
|
function shouldRollover(bytes) {
|
|
74
102
|
return offset + bytes.length > maxSize;
|
|
75
103
|
}
|
|
@@ -86,29 +114,79 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
86
114
|
offset = 0;
|
|
87
115
|
fd = options.openSync(path);
|
|
88
116
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
if (!options.nonBlocking) {
|
|
118
|
+
function flushBuffer$1() {
|
|
119
|
+
if (buffer.length > 0) {
|
|
120
|
+
const bytes = encoder.encode(buffer);
|
|
121
|
+
buffer = "";
|
|
122
|
+
if (shouldRollover(bytes)) performRollover();
|
|
123
|
+
options.writeSync(fd, bytes);
|
|
124
|
+
options.flushSync(fd);
|
|
125
|
+
offset += bytes.length;
|
|
126
|
+
lastFlushTimestamp = Date.now();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const sink = (record) => {
|
|
130
|
+
buffer += formatter(record);
|
|
131
|
+
const shouldFlushBySize = buffer.length >= bufferSize;
|
|
132
|
+
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
|
|
133
|
+
if (shouldFlushBySize || shouldFlushByTime) flushBuffer$1();
|
|
134
|
+
};
|
|
135
|
+
sink[Symbol.dispose] = () => {
|
|
136
|
+
flushBuffer$1();
|
|
137
|
+
options.closeSync(fd);
|
|
138
|
+
};
|
|
139
|
+
return sink;
|
|
140
|
+
}
|
|
141
|
+
const asyncOptions = options;
|
|
142
|
+
let disposed = false;
|
|
143
|
+
let activeFlush = null;
|
|
144
|
+
let flushTimer = null;
|
|
145
|
+
async function flushBuffer() {
|
|
146
|
+
if (buffer.length === 0) return;
|
|
147
|
+
const data = buffer;
|
|
148
|
+
buffer = "";
|
|
149
|
+
try {
|
|
150
|
+
const bytes = encoder.encode(data);
|
|
93
151
|
if (shouldRollover(bytes)) performRollover();
|
|
94
|
-
|
|
95
|
-
|
|
152
|
+
asyncOptions.writeSync(fd, bytes);
|
|
153
|
+
await asyncOptions.flush(fd);
|
|
96
154
|
offset += bytes.length;
|
|
97
155
|
lastFlushTimestamp = Date.now();
|
|
98
|
-
}
|
|
156
|
+
} catch {}
|
|
99
157
|
}
|
|
100
|
-
|
|
101
|
-
|
|
158
|
+
function scheduleFlush() {
|
|
159
|
+
if (activeFlush || disposed) return;
|
|
160
|
+
activeFlush = flushBuffer().finally(() => {
|
|
161
|
+
activeFlush = null;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function startFlushTimer() {
|
|
165
|
+
if (flushTimer !== null || disposed) return;
|
|
166
|
+
flushTimer = setInterval(() => {
|
|
167
|
+
scheduleFlush();
|
|
168
|
+
}, flushInterval);
|
|
169
|
+
}
|
|
170
|
+
const nonBlockingSink = (record) => {
|
|
171
|
+
if (disposed) return;
|
|
102
172
|
buffer += formatter(record);
|
|
103
173
|
const shouldFlushBySize = buffer.length >= bufferSize;
|
|
104
174
|
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
|
|
105
|
-
if (shouldFlushBySize || shouldFlushByTime)
|
|
175
|
+
if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
|
|
176
|
+
else if (flushTimer === null && flushInterval > 0) startFlushTimer();
|
|
106
177
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
178
|
+
nonBlockingSink[Symbol.asyncDispose] = async () => {
|
|
179
|
+
disposed = true;
|
|
180
|
+
if (flushTimer !== null) {
|
|
181
|
+
clearInterval(flushTimer);
|
|
182
|
+
flushTimer = null;
|
|
183
|
+
}
|
|
184
|
+
await flushBuffer();
|
|
185
|
+
try {
|
|
186
|
+
await asyncOptions.close(fd);
|
|
187
|
+
} catch {}
|
|
110
188
|
};
|
|
111
|
-
return
|
|
189
|
+
return nonBlockingSink;
|
|
112
190
|
}
|
|
113
191
|
|
|
114
192
|
//#endregion
|
package/dist/filesink.base.d.cts
CHANGED
|
@@ -26,6 +26,15 @@ type FileSinkOptions = StreamSinkOptions & {
|
|
|
26
26
|
* @since 0.12.0
|
|
27
27
|
*/
|
|
28
28
|
flushInterval?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Enable non-blocking mode with background flushing.
|
|
31
|
+
* When enabled, flush operations are performed asynchronously to prevent
|
|
32
|
+
* blocking the main thread during file I/O operations.
|
|
33
|
+
*
|
|
34
|
+
* @default `false`
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
*/
|
|
37
|
+
nonBlocking?: boolean;
|
|
29
38
|
};
|
|
30
39
|
/**
|
|
31
40
|
* A platform-specific file sink driver.
|
|
@@ -54,6 +63,23 @@ interface FileSinkDriver<TFile> {
|
|
|
54
63
|
*/
|
|
55
64
|
closeSync(fd: TFile): void;
|
|
56
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* A platform-specific async file sink driver.
|
|
68
|
+
* @typeParam TFile The type of the file descriptor.
|
|
69
|
+
* @since 1.0.0
|
|
70
|
+
*/
|
|
71
|
+
interface AsyncFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
|
|
72
|
+
/**
|
|
73
|
+
* Asynchronously flush the file to ensure that all data is written to the disk.
|
|
74
|
+
* @param fd The file descriptor.
|
|
75
|
+
*/
|
|
76
|
+
flush(fd: TFile): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Asynchronously close the file.
|
|
79
|
+
* @param fd The file descriptor.
|
|
80
|
+
*/
|
|
81
|
+
close(fd: TFile): Promise<void>;
|
|
82
|
+
}
|
|
57
83
|
/**
|
|
58
84
|
* Get a platform-independent file sink.
|
|
59
85
|
*
|
|
@@ -61,7 +87,8 @@ interface FileSinkDriver<TFile> {
|
|
|
61
87
|
* @param path A path to the file to write to.
|
|
62
88
|
* @param options The options for the sink and the file driver.
|
|
63
89
|
* @returns A sink that writes to the file. The sink is also a disposable
|
|
64
|
-
* object that closes the file when disposed.
|
|
90
|
+
* object that closes the file when disposed. If `nonBlocking` is enabled,
|
|
91
|
+
* returns a sink that also implements {@link AsyncDisposable}.
|
|
65
92
|
*/
|
|
66
93
|
|
|
67
94
|
/**
|
|
@@ -96,6 +123,26 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
|
|
|
96
123
|
*/
|
|
97
124
|
renameSync(oldPath: string, newPath: string): void;
|
|
98
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* A platform-specific async rotating file sink driver.
|
|
128
|
+
* @since 1.0.0
|
|
129
|
+
*/
|
|
130
|
+
interface AsyncRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile> {
|
|
131
|
+
/**
|
|
132
|
+
* Get the size of the file.
|
|
133
|
+
* @param path A path to the file.
|
|
134
|
+
* @returns The `size` of the file in bytes, in an object.
|
|
135
|
+
*/
|
|
136
|
+
statSync(path: string): {
|
|
137
|
+
size: number;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Rename a file.
|
|
141
|
+
* @param oldPath A path to the file to rename.
|
|
142
|
+
* @param newPath A path to be renamed to.
|
|
143
|
+
*/
|
|
144
|
+
renameSync(oldPath: string, newPath: string): void;
|
|
145
|
+
}
|
|
99
146
|
/**
|
|
100
147
|
* Get a platform-independent rotating file sink.
|
|
101
148
|
*
|
|
@@ -107,8 +154,9 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
|
|
|
107
154
|
* @param path A path to the file to write to.
|
|
108
155
|
* @param options The options for the sink and the file driver.
|
|
109
156
|
* @returns A sink that writes to the file. The sink is also a disposable
|
|
110
|
-
* object that closes the file when disposed.
|
|
157
|
+
* object that closes the file when disposed. If `nonBlocking` is enabled,
|
|
158
|
+
* returns a sink that also implements {@link AsyncDisposable}.
|
|
111
159
|
*/
|
|
112
160
|
//#endregion
|
|
113
|
-
export { FileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions };
|
|
161
|
+
export { AsyncRotatingFileSinkDriver, FileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions };
|
|
114
162
|
//# sourceMappingURL=filesink.base.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesink.base.d.cts","names":[],"sources":["../filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAUA;
|
|
1
|
+
{"version":3,"file":"filesink.base.d.cts","names":[],"sources":["../filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAUA;AAuCiB,KAvCL,eAAA,GAAkB,iBAuCC,GAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;AAYnB;AAQrB;;;;EAKiB,UAAG,CAAA,EAAA,MAAA;EAAO;;;AALuC;AA0JlE;;;EAAqE,aAApB,CAAA,EAAA,MAAA;EAAI;AAerD;;;;AAAqE;AAoBrE;;EAA4C,WACd,CAAA,EAAA,OAAA;CAAK;AAAN;;;;UA9NZ;;;;;0BAKS;;;;;;gBAOV,cAAc;;;;;gBAMd;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;YAKvD,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA+IH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;UAoBrD,2CACP,oBAAoB"}
|
package/dist/filesink.base.d.ts
CHANGED
|
@@ -26,6 +26,15 @@ type FileSinkOptions = StreamSinkOptions & {
|
|
|
26
26
|
* @since 0.12.0
|
|
27
27
|
*/
|
|
28
28
|
flushInterval?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Enable non-blocking mode with background flushing.
|
|
31
|
+
* When enabled, flush operations are performed asynchronously to prevent
|
|
32
|
+
* blocking the main thread during file I/O operations.
|
|
33
|
+
*
|
|
34
|
+
* @default `false`
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
*/
|
|
37
|
+
nonBlocking?: boolean;
|
|
29
38
|
};
|
|
30
39
|
/**
|
|
31
40
|
* A platform-specific file sink driver.
|
|
@@ -54,6 +63,23 @@ interface FileSinkDriver<TFile> {
|
|
|
54
63
|
*/
|
|
55
64
|
closeSync(fd: TFile): void;
|
|
56
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* A platform-specific async file sink driver.
|
|
68
|
+
* @typeParam TFile The type of the file descriptor.
|
|
69
|
+
* @since 1.0.0
|
|
70
|
+
*/
|
|
71
|
+
interface AsyncFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
|
|
72
|
+
/**
|
|
73
|
+
* Asynchronously flush the file to ensure that all data is written to the disk.
|
|
74
|
+
* @param fd The file descriptor.
|
|
75
|
+
*/
|
|
76
|
+
flush(fd: TFile): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Asynchronously close the file.
|
|
79
|
+
* @param fd The file descriptor.
|
|
80
|
+
*/
|
|
81
|
+
close(fd: TFile): Promise<void>;
|
|
82
|
+
}
|
|
57
83
|
/**
|
|
58
84
|
* Get a platform-independent file sink.
|
|
59
85
|
*
|
|
@@ -61,7 +87,8 @@ interface FileSinkDriver<TFile> {
|
|
|
61
87
|
* @param path A path to the file to write to.
|
|
62
88
|
* @param options The options for the sink and the file driver.
|
|
63
89
|
* @returns A sink that writes to the file. The sink is also a disposable
|
|
64
|
-
* object that closes the file when disposed.
|
|
90
|
+
* object that closes the file when disposed. If `nonBlocking` is enabled,
|
|
91
|
+
* returns a sink that also implements {@link AsyncDisposable}.
|
|
65
92
|
*/
|
|
66
93
|
|
|
67
94
|
/**
|
|
@@ -96,6 +123,26 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
|
|
|
96
123
|
*/
|
|
97
124
|
renameSync(oldPath: string, newPath: string): void;
|
|
98
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* A platform-specific async rotating file sink driver.
|
|
128
|
+
* @since 1.0.0
|
|
129
|
+
*/
|
|
130
|
+
interface AsyncRotatingFileSinkDriver<TFile> extends AsyncFileSinkDriver<TFile> {
|
|
131
|
+
/**
|
|
132
|
+
* Get the size of the file.
|
|
133
|
+
* @param path A path to the file.
|
|
134
|
+
* @returns The `size` of the file in bytes, in an object.
|
|
135
|
+
*/
|
|
136
|
+
statSync(path: string): {
|
|
137
|
+
size: number;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Rename a file.
|
|
141
|
+
* @param oldPath A path to the file to rename.
|
|
142
|
+
* @param newPath A path to be renamed to.
|
|
143
|
+
*/
|
|
144
|
+
renameSync(oldPath: string, newPath: string): void;
|
|
145
|
+
}
|
|
99
146
|
/**
|
|
100
147
|
* Get a platform-independent rotating file sink.
|
|
101
148
|
*
|
|
@@ -107,8 +154,9 @@ interface RotatingFileSinkDriver<TFile> extends FileSinkDriver<TFile> {
|
|
|
107
154
|
* @param path A path to the file to write to.
|
|
108
155
|
* @param options The options for the sink and the file driver.
|
|
109
156
|
* @returns A sink that writes to the file. The sink is also a disposable
|
|
110
|
-
* object that closes the file when disposed.
|
|
157
|
+
* object that closes the file when disposed. If `nonBlocking` is enabled,
|
|
158
|
+
* returns a sink that also implements {@link AsyncDisposable}.
|
|
111
159
|
*/
|
|
112
160
|
//#endregion
|
|
113
|
-
export { FileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions };
|
|
161
|
+
export { AsyncRotatingFileSinkDriver, FileSinkDriver, FileSinkOptions, RotatingFileSinkDriver, RotatingFileSinkOptions };
|
|
114
162
|
//# sourceMappingURL=filesink.base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesink.base.d.ts","names":[],"sources":["../filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAUA;
|
|
1
|
+
{"version":3,"file":"filesink.base.d.ts","names":[],"sources":["../filesink.base.ts"],"sourcesContent":[],"mappings":";;;;;;AAUA;AAuCiB,KAvCL,eAAA,GAAkB,iBAuCC,GAAA;EAAA;;;EAYV,IAAS,CAAA,EAAA,OAAA;EAAU;;AAYnB;AAQrB;;;;EAKiB,UAAG,CAAA,EAAA,MAAA;EAAO;;;AALuC;AA0JlE;;;EAAqE,aAApB,CAAA,EAAA,MAAA;EAAI;AAerD;;;;AAAqE;AAoBrE;;EAA4C,WACd,CAAA,EAAA,OAAA;CAAK;AAAN;;;;UA9NZ;;;;;0BAKS;;;;;;gBAOV,cAAc;;;;;gBAMd;;;;;gBAMA;;;;;;;UAQC,mCAAmC,eAAe;;;;;YAKvD,QAAQ;;;;;YAMR,QAAQ;;;;;;;;;;;;;;;;UA+IH,uBAAA,SAAgC,KAAK;;;;;;;;;;;;;UAerC,sCAAsC,eAAe;;;;;;;;;;;;;;;;;;;;UAoBrD,2CACP,oBAAoB"}
|
package/dist/filesink.base.js
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { defaultTextFormatter } from "@logtape/logtape";
|
|
2
2
|
|
|
3
3
|
//#region filesink.base.ts
|
|
4
|
-
/**
|
|
5
|
-
* Get a platform-independent file sink.
|
|
6
|
-
*
|
|
7
|
-
* @typeParam TFile The type of the file descriptor.
|
|
8
|
-
* @param path A path to the file to write to.
|
|
9
|
-
* @param options The options for the sink and the file driver.
|
|
10
|
-
* @returns A sink that writes to the file. The sink is also a disposable
|
|
11
|
-
* object that closes the file when disposed.
|
|
12
|
-
*/
|
|
13
4
|
function getBaseFileSink(path, options) {
|
|
14
5
|
const formatter = options.formatter ?? defaultTextFormatter;
|
|
15
6
|
const encoder = options.encoder ?? new TextEncoder();
|
|
@@ -18,43 +9,79 @@ function getBaseFileSink(path, options) {
|
|
|
18
9
|
let fd = options.lazy ? null : options.openSync(path);
|
|
19
10
|
let buffer = "";
|
|
20
11
|
let lastFlushTimestamp = Date.now();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
12
|
+
if (!options.nonBlocking) {
|
|
13
|
+
function flushBuffer$1() {
|
|
14
|
+
if (fd == null) return;
|
|
15
|
+
if (buffer.length > 0) {
|
|
16
|
+
options.writeSync(fd, encoder.encode(buffer));
|
|
17
|
+
buffer = "";
|
|
18
|
+
options.flushSync(fd);
|
|
19
|
+
lastFlushTimestamp = Date.now();
|
|
20
|
+
}
|
|
28
21
|
}
|
|
22
|
+
const sink = (record) => {
|
|
23
|
+
if (fd == null) fd = options.openSync(path);
|
|
24
|
+
buffer += formatter(record);
|
|
25
|
+
const shouldFlushBySize = buffer.length >= bufferSize;
|
|
26
|
+
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
|
|
27
|
+
if (shouldFlushBySize || shouldFlushByTime) flushBuffer$1();
|
|
28
|
+
};
|
|
29
|
+
sink[Symbol.dispose] = () => {
|
|
30
|
+
if (fd !== null) {
|
|
31
|
+
flushBuffer$1();
|
|
32
|
+
options.closeSync(fd);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return sink;
|
|
36
|
+
}
|
|
37
|
+
const asyncOptions = options;
|
|
38
|
+
let disposed = false;
|
|
39
|
+
let activeFlush = null;
|
|
40
|
+
let flushTimer = null;
|
|
41
|
+
async function flushBuffer() {
|
|
42
|
+
if (fd == null || buffer.length === 0) return;
|
|
43
|
+
const data = buffer;
|
|
44
|
+
buffer = "";
|
|
45
|
+
try {
|
|
46
|
+
asyncOptions.writeSync(fd, encoder.encode(data));
|
|
47
|
+
await asyncOptions.flush(fd);
|
|
48
|
+
lastFlushTimestamp = Date.now();
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
function scheduleFlush() {
|
|
52
|
+
if (activeFlush || disposed) return;
|
|
53
|
+
activeFlush = flushBuffer().finally(() => {
|
|
54
|
+
activeFlush = null;
|
|
55
|
+
});
|
|
29
56
|
}
|
|
30
|
-
|
|
31
|
-
if (
|
|
57
|
+
function startFlushTimer() {
|
|
58
|
+
if (flushTimer !== null || disposed) return;
|
|
59
|
+
flushTimer = setInterval(() => {
|
|
60
|
+
scheduleFlush();
|
|
61
|
+
}, flushInterval);
|
|
62
|
+
}
|
|
63
|
+
const nonBlockingSink = (record) => {
|
|
64
|
+
if (disposed) return;
|
|
65
|
+
if (fd == null) fd = asyncOptions.openSync(path);
|
|
32
66
|
buffer += formatter(record);
|
|
33
67
|
const shouldFlushBySize = buffer.length >= bufferSize;
|
|
34
68
|
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
|
|
35
|
-
if (shouldFlushBySize || shouldFlushByTime)
|
|
69
|
+
if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
|
|
70
|
+
else if (flushTimer === null && flushInterval > 0) startFlushTimer();
|
|
36
71
|
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
nonBlockingSink[Symbol.asyncDispose] = async () => {
|
|
73
|
+
disposed = true;
|
|
74
|
+
if (flushTimer !== null) {
|
|
75
|
+
clearInterval(flushTimer);
|
|
76
|
+
flushTimer = null;
|
|
41
77
|
}
|
|
78
|
+
await flushBuffer();
|
|
79
|
+
if (fd !== null) try {
|
|
80
|
+
await asyncOptions.close(fd);
|
|
81
|
+
} catch {}
|
|
42
82
|
};
|
|
43
|
-
return
|
|
83
|
+
return nonBlockingSink;
|
|
44
84
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Get a platform-independent rotating file sink.
|
|
47
|
-
*
|
|
48
|
-
* This sink writes log records to a file, and rotates the file when it reaches
|
|
49
|
-
* the `maxSize`. The rotated files are named with the original file name
|
|
50
|
-
* followed by a dot and a number, starting from 1. The number is incremented
|
|
51
|
-
* for each rotation, and the maximum number of files to keep is `maxFiles`.
|
|
52
|
-
*
|
|
53
|
-
* @param path A path to the file to write to.
|
|
54
|
-
* @param options The options for the sink and the file driver.
|
|
55
|
-
* @returns A sink that writes to the file. The sink is also a disposable
|
|
56
|
-
* object that closes the file when disposed.
|
|
57
|
-
*/
|
|
58
85
|
function getBaseRotatingFileSink(path, options) {
|
|
59
86
|
const formatter = options.formatter ?? defaultTextFormatter;
|
|
60
87
|
const encoder = options.encoder ?? new TextEncoder();
|
|
@@ -69,6 +96,7 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
69
96
|
} catch {}
|
|
70
97
|
let fd = options.openSync(path);
|
|
71
98
|
let lastFlushTimestamp = Date.now();
|
|
99
|
+
let buffer = "";
|
|
72
100
|
function shouldRollover(bytes) {
|
|
73
101
|
return offset + bytes.length > maxSize;
|
|
74
102
|
}
|
|
@@ -85,29 +113,79 @@ function getBaseRotatingFileSink(path, options) {
|
|
|
85
113
|
offset = 0;
|
|
86
114
|
fd = options.openSync(path);
|
|
87
115
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
116
|
+
if (!options.nonBlocking) {
|
|
117
|
+
function flushBuffer$1() {
|
|
118
|
+
if (buffer.length > 0) {
|
|
119
|
+
const bytes = encoder.encode(buffer);
|
|
120
|
+
buffer = "";
|
|
121
|
+
if (shouldRollover(bytes)) performRollover();
|
|
122
|
+
options.writeSync(fd, bytes);
|
|
123
|
+
options.flushSync(fd);
|
|
124
|
+
offset += bytes.length;
|
|
125
|
+
lastFlushTimestamp = Date.now();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const sink = (record) => {
|
|
129
|
+
buffer += formatter(record);
|
|
130
|
+
const shouldFlushBySize = buffer.length >= bufferSize;
|
|
131
|
+
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
|
|
132
|
+
if (shouldFlushBySize || shouldFlushByTime) flushBuffer$1();
|
|
133
|
+
};
|
|
134
|
+
sink[Symbol.dispose] = () => {
|
|
135
|
+
flushBuffer$1();
|
|
136
|
+
options.closeSync(fd);
|
|
137
|
+
};
|
|
138
|
+
return sink;
|
|
139
|
+
}
|
|
140
|
+
const asyncOptions = options;
|
|
141
|
+
let disposed = false;
|
|
142
|
+
let activeFlush = null;
|
|
143
|
+
let flushTimer = null;
|
|
144
|
+
async function flushBuffer() {
|
|
145
|
+
if (buffer.length === 0) return;
|
|
146
|
+
const data = buffer;
|
|
147
|
+
buffer = "";
|
|
148
|
+
try {
|
|
149
|
+
const bytes = encoder.encode(data);
|
|
92
150
|
if (shouldRollover(bytes)) performRollover();
|
|
93
|
-
|
|
94
|
-
|
|
151
|
+
asyncOptions.writeSync(fd, bytes);
|
|
152
|
+
await asyncOptions.flush(fd);
|
|
95
153
|
offset += bytes.length;
|
|
96
154
|
lastFlushTimestamp = Date.now();
|
|
97
|
-
}
|
|
155
|
+
} catch {}
|
|
98
156
|
}
|
|
99
|
-
|
|
100
|
-
|
|
157
|
+
function scheduleFlush() {
|
|
158
|
+
if (activeFlush || disposed) return;
|
|
159
|
+
activeFlush = flushBuffer().finally(() => {
|
|
160
|
+
activeFlush = null;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function startFlushTimer() {
|
|
164
|
+
if (flushTimer !== null || disposed) return;
|
|
165
|
+
flushTimer = setInterval(() => {
|
|
166
|
+
scheduleFlush();
|
|
167
|
+
}, flushInterval);
|
|
168
|
+
}
|
|
169
|
+
const nonBlockingSink = (record) => {
|
|
170
|
+
if (disposed) return;
|
|
101
171
|
buffer += formatter(record);
|
|
102
172
|
const shouldFlushBySize = buffer.length >= bufferSize;
|
|
103
173
|
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
|
|
104
|
-
if (shouldFlushBySize || shouldFlushByTime)
|
|
174
|
+
if (shouldFlushBySize || shouldFlushByTime) scheduleFlush();
|
|
175
|
+
else if (flushTimer === null && flushInterval > 0) startFlushTimer();
|
|
105
176
|
};
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
177
|
+
nonBlockingSink[Symbol.asyncDispose] = async () => {
|
|
178
|
+
disposed = true;
|
|
179
|
+
if (flushTimer !== null) {
|
|
180
|
+
clearInterval(flushTimer);
|
|
181
|
+
flushTimer = null;
|
|
182
|
+
}
|
|
183
|
+
await flushBuffer();
|
|
184
|
+
try {
|
|
185
|
+
await asyncOptions.close(fd);
|
|
186
|
+
} catch {}
|
|
109
187
|
};
|
|
110
|
-
return
|
|
188
|
+
return nonBlockingSink;
|
|
111
189
|
}
|
|
112
190
|
|
|
113
191
|
//#endregion
|