befly 3.60.0 → 3.62.0
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/checks/config.js +5 -5
- package/configs/beflyConfig.json +1 -4
- package/index.js +6 -0
- package/lib/logger.js +122 -298
- package/package.json +1 -1
- package/plugins/logger.js +2 -5
- package/utils/loggerUtils.js +0 -3
package/checks/config.js
CHANGED
|
@@ -63,10 +63,7 @@ const configSchema = z
|
|
|
63
63
|
logger: z
|
|
64
64
|
.object({
|
|
65
65
|
debug: boolIntSchema,
|
|
66
|
-
excludeFields: z.array(noTrimString)
|
|
67
|
-
dir: noTrimString,
|
|
68
|
-
console: boolIntSchema,
|
|
69
|
-
maxSize: z.int().min(1)
|
|
66
|
+
excludeFields: z.array(noTrimString)
|
|
70
67
|
})
|
|
71
68
|
.strict(),
|
|
72
69
|
|
|
@@ -78,7 +75,10 @@ const configSchema = z
|
|
|
78
75
|
password: noTrimString,
|
|
79
76
|
database: noTrimString,
|
|
80
77
|
max: z.number().min(1),
|
|
81
|
-
beflyMode: beflyModeSchema.optional()
|
|
78
|
+
beflyMode: beflyModeSchema.optional(),
|
|
79
|
+
idleTimeout: z.number().min(1).optional(),
|
|
80
|
+
maxLifetime: z.number().min(0).optional(),
|
|
81
|
+
connectionTimeout: z.number().min(1).optional()
|
|
82
82
|
})
|
|
83
83
|
.strict(),
|
|
84
84
|
|
package/configs/beflyConfig.json
CHANGED
|
@@ -19,10 +19,7 @@
|
|
|
19
19
|
"excludeApisLog": ["/api/core/tongJi/*Report"],
|
|
20
20
|
"logger": {
|
|
21
21
|
"debug": 1,
|
|
22
|
-
"excludeFields": ["password", "token", "secret"]
|
|
23
|
-
"dir": "./logs",
|
|
24
|
-
"console": 1,
|
|
25
|
-
"maxSize": 20
|
|
22
|
+
"excludeFields": ["password", "token", "secret"]
|
|
26
23
|
},
|
|
27
24
|
|
|
28
25
|
"mysql": {
|
package/index.js
CHANGED
|
@@ -138,6 +138,12 @@ export async function createBefly(env = {}, config = {}, menus = []) {
|
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// 在插件加载前用项目 runMode 初始化 Logger,确保后续所有日志目录正确
|
|
142
|
+
Logger.configure({
|
|
143
|
+
runtimeEnv: mergedConfig.runMode,
|
|
144
|
+
...mergedConfig.logger
|
|
145
|
+
});
|
|
146
|
+
|
|
141
147
|
return new Befly({
|
|
142
148
|
env: env,
|
|
143
149
|
config: mergedConfig,
|
package/lib/logger.js
CHANGED
|
@@ -2,19 +2,18 @@
|
|
|
2
2
|
* 日志系统 - Bun 环境自定义实现(替换 pino / pino-roll)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { createWriteStream, existsSync, mkdirSync } from "node:fs";
|
|
6
|
-
import { stat } from "node:fs/promises";
|
|
5
|
+
import { createWriteStream, existsSync, mkdirSync, rmSync, statSync } from "node:fs";
|
|
7
6
|
import { join as nodePathJoin, resolve as nodePathResolve } from "node:path";
|
|
8
7
|
|
|
9
8
|
import { formatYmdHms } from "../utils/datetime.js";
|
|
10
|
-
import {
|
|
9
|
+
import { isPlainObject } from "../utils/is.js";
|
|
11
10
|
import { buildSensitiveKeyMatcher, sanitizeLogObject } from "../utils/loggerUtils.js";
|
|
12
11
|
import { normalizePositiveInt } from "../utils/util.js";
|
|
13
12
|
|
|
14
13
|
// 注意:Logger 可能在运行时/测试中被 process.chdir() 影响。
|
|
15
14
|
// 为避免相对路径的 logs 目录随着 cwd 变化,使用模块加载时的初始 cwd 作为锚点。
|
|
16
|
-
const
|
|
17
|
-
const
|
|
15
|
+
const DEFAULT_MAX_SIZE_MB = 20;
|
|
16
|
+
const MAX_FILE_BYTES = DEFAULT_MAX_SIZE_MB * 1024 * 1024;
|
|
18
17
|
|
|
19
18
|
const BUILTIN_SENSITIVE_KEYS = ["*password*", "pass", "pwd", "*token*", "access_token", "refresh_token", "accessToken", "refreshToken", "authorization", "cookie", "set-cookie", "*secret*", "apiKey", "api_key", "privateKey", "private_key"];
|
|
20
19
|
|
|
@@ -32,168 +31,79 @@ let errorFileSink = null;
|
|
|
32
31
|
|
|
33
32
|
let config = {
|
|
34
33
|
debug: 0,
|
|
35
|
-
dir:
|
|
36
|
-
maxSize: 20,
|
|
34
|
+
dir: "",
|
|
37
35
|
runtimeEnv: "production"
|
|
38
36
|
};
|
|
39
37
|
|
|
40
38
|
function safeWriteStderr(msg) {
|
|
41
|
-
|
|
42
|
-
process.stderr.write(`${msg}\n`);
|
|
43
|
-
} catch {
|
|
44
|
-
// ignore
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function shiftBatchFromPending(pending, maxBatchBytes) {
|
|
49
|
-
const parts = [];
|
|
50
|
-
let bytes = 0;
|
|
51
|
-
|
|
52
|
-
while (pending.length > 0) {
|
|
53
|
-
const next = pending[0];
|
|
54
|
-
const nextBytes = Buffer.byteLength(next);
|
|
55
|
-
if (parts.length > 0 && bytes + nextBytes > maxBatchBytes) {
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
parts.push(next);
|
|
59
|
-
bytes = bytes + nextBytes;
|
|
60
|
-
pending.shift();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return { chunk: parts.join(""), bytes: bytes };
|
|
39
|
+
process.stderr.write(`${msg}\n`);
|
|
64
40
|
}
|
|
65
41
|
|
|
66
42
|
class LogFileSink {
|
|
67
|
-
constructor(
|
|
68
|
-
this.prefix =
|
|
69
|
-
this.maxFileBytes = options.maxFileBytes;
|
|
43
|
+
constructor(prefix) {
|
|
44
|
+
this.prefix = prefix;
|
|
70
45
|
|
|
71
46
|
this.stream = null;
|
|
72
47
|
this.streamDate = "";
|
|
73
48
|
this.streamIndex = 0;
|
|
74
49
|
this.streamSizeBytes = 0;
|
|
75
50
|
this.disabled = false;
|
|
76
|
-
|
|
77
|
-
this.pending = [];
|
|
78
|
-
this.pendingBytes = 0;
|
|
79
|
-
this.scheduledTimer = null;
|
|
80
|
-
this.flushing = false;
|
|
81
|
-
|
|
82
|
-
this.maxBufferBytes = 10 * 1024 * 1024;
|
|
83
|
-
this.flushDelayMs = 10;
|
|
84
|
-
this.maxBatchBytes = 64 * 1024;
|
|
85
51
|
}
|
|
86
52
|
|
|
87
|
-
|
|
53
|
+
write(line) {
|
|
88
54
|
if (this.disabled) return;
|
|
89
55
|
|
|
90
|
-
|
|
91
|
-
if (this.
|
|
92
|
-
|
|
93
|
-
|
|
56
|
+
this.ensureStreamReady(line.length);
|
|
57
|
+
if (this.stream) {
|
|
58
|
+
this.stream.write(line);
|
|
59
|
+
this.streamSizeBytes += line.length;
|
|
94
60
|
}
|
|
95
|
-
|
|
96
|
-
this.pending.push(line);
|
|
97
|
-
this.pendingBytes = this.pendingBytes + bytes;
|
|
98
|
-
|
|
99
|
-
this.scheduleFlush();
|
|
100
61
|
}
|
|
101
62
|
|
|
102
63
|
async flushNow() {
|
|
103
|
-
this.
|
|
104
|
-
await this.flush();
|
|
64
|
+
return this.closeStream();
|
|
105
65
|
}
|
|
106
66
|
|
|
107
67
|
async shutdown() {
|
|
108
|
-
this.
|
|
109
|
-
await this.flush();
|
|
110
|
-
await this.closeStream();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
scheduleFlush() {
|
|
114
|
-
if (this.scheduledTimer) return;
|
|
115
|
-
|
|
116
|
-
this.scheduledTimer = setTimeout(() => {
|
|
117
|
-
// timer 触发时先清空句柄,避免 flush 内再次 schedule 时被认为“已安排”。
|
|
118
|
-
this.scheduledTimer = null;
|
|
119
|
-
this.flush();
|
|
120
|
-
}, this.flushDelayMs);
|
|
68
|
+
return this.closeStream();
|
|
121
69
|
}
|
|
122
70
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
71
|
+
getFilePath(date, index) {
|
|
72
|
+
const suffix = index > 0 ? `.${index}` : "";
|
|
73
|
+
const filename = this.prefix === "app" ? `${date}${suffix}.log` : `${this.prefix}.${date}${suffix}.log`;
|
|
74
|
+
return nodePathJoin(config.dir, filename);
|
|
127
75
|
}
|
|
128
76
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.flushing = true;
|
|
77
|
+
ensureStreamReady(nextChunkBytes) {
|
|
78
|
+
const date = formatYmdHms(Date.now(), "date");
|
|
132
79
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const batch = shiftBatchFromPending(this.pending, this.maxBatchBytes);
|
|
136
|
-
this.pendingBytes = this.pendingBytes - batch.bytes;
|
|
137
|
-
|
|
138
|
-
const ok = await this.writeChunk(batch.chunk, batch.bytes);
|
|
139
|
-
if (!ok) {
|
|
140
|
-
// writer 已禁用/失败:清空剩余 pending
|
|
141
|
-
this.pending = [];
|
|
142
|
-
this.pendingBytes = 0;
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
} finally {
|
|
147
|
-
this.flushing = false;
|
|
148
|
-
if (this.pending.length > 0) {
|
|
149
|
-
this.scheduleFlush();
|
|
150
|
-
}
|
|
80
|
+
if (this.stream && this.streamDate && date !== this.streamDate) {
|
|
81
|
+
this.closeStreamSync();
|
|
151
82
|
}
|
|
152
|
-
}
|
|
153
83
|
|
|
154
|
-
|
|
155
|
-
|
|
84
|
+
if (!this.stream) {
|
|
85
|
+
this.streamDate = date;
|
|
86
|
+
this.streamIndex = 0;
|
|
87
|
+
this.streamSizeBytes = this.getFileSize(this.getFilePath(date, 0));
|
|
156
88
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (!this.stream) {
|
|
160
|
-
// 文件 sink 已被禁用或打开失败
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
89
|
+
this.openStream(this.getFilePath(date, 0));
|
|
90
|
+
}
|
|
163
91
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
stream.once("drain", () => resolve());
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
this.streamSizeBytes = this.streamSizeBytes + chunkBytes;
|
|
176
|
-
return true;
|
|
177
|
-
} catch (error) {
|
|
178
|
-
safeWriteStderr(`[Logger] file sink flush error (${this.prefix}): ${error?.message || error}`);
|
|
179
|
-
this.disabled = true;
|
|
180
|
-
await this.closeStream();
|
|
181
|
-
return false;
|
|
92
|
+
if (this.stream && this.streamSizeBytes + nextChunkBytes > MAX_FILE_BYTES) {
|
|
93
|
+
this.closeStreamSync();
|
|
94
|
+
this.streamIndex += 1;
|
|
95
|
+
this.streamSizeBytes = 0;
|
|
96
|
+
|
|
97
|
+
this.openStream(this.getFilePath(date, this.streamIndex));
|
|
182
98
|
}
|
|
183
99
|
}
|
|
184
100
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
st.end(() => resolve());
|
|
193
|
-
} catch {
|
|
194
|
-
resolve();
|
|
195
|
-
}
|
|
196
|
-
});
|
|
101
|
+
getFileSize(filePath) {
|
|
102
|
+
try {
|
|
103
|
+
return statSync(filePath).size;
|
|
104
|
+
} catch {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
197
107
|
}
|
|
198
108
|
|
|
199
109
|
openStream(filePath) {
|
|
@@ -202,7 +112,7 @@ class LogFileSink {
|
|
|
202
112
|
this.stream.on("error", (error) => {
|
|
203
113
|
safeWriteStderr(`[Logger] file sink error (${this.prefix}): ${error?.message || error}`);
|
|
204
114
|
this.disabled = true;
|
|
205
|
-
this.
|
|
115
|
+
this.closeStreamSync();
|
|
206
116
|
});
|
|
207
117
|
} catch (error) {
|
|
208
118
|
safeWriteStderr(`[Logger] createWriteStream failed (${this.prefix}): ${error?.message || error}`);
|
|
@@ -211,91 +121,51 @@ class LogFileSink {
|
|
|
211
121
|
}
|
|
212
122
|
}
|
|
213
123
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return nodePathJoin(RUNTIME_LOG_DIR, filename);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async ensureStreamReady(nextChunkBytes) {
|
|
221
|
-
const date = formatYmdHms(Date.now(), "date");
|
|
222
|
-
|
|
223
|
-
// 日期变化:切新文件
|
|
224
|
-
if (this.stream && this.streamDate && date !== this.streamDate) {
|
|
225
|
-
await this.closeStream();
|
|
226
|
-
this.streamDate = "";
|
|
227
|
-
this.streamIndex = 0;
|
|
228
|
-
this.streamSizeBytes = 0;
|
|
124
|
+
closeStreamSync() {
|
|
125
|
+
if (this.stream) {
|
|
126
|
+
this.stream.end();
|
|
229
127
|
}
|
|
128
|
+
this.stream = null;
|
|
129
|
+
this.streamDate = "";
|
|
130
|
+
this.streamIndex = 0;
|
|
131
|
+
this.streamSizeBytes = 0;
|
|
132
|
+
}
|
|
230
133
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
size = isNumber(st.size) ? st.size : 0;
|
|
238
|
-
} catch {
|
|
239
|
-
size = 0;
|
|
134
|
+
closeStream() {
|
|
135
|
+
return new Promise((resolve) => {
|
|
136
|
+
const st = this.stream;
|
|
137
|
+
if (!st) {
|
|
138
|
+
resolve();
|
|
139
|
+
return;
|
|
240
140
|
}
|
|
241
|
-
|
|
242
|
-
this.streamDate =
|
|
141
|
+
this.stream = null;
|
|
142
|
+
this.streamDate = "";
|
|
243
143
|
this.streamIndex = 0;
|
|
244
|
-
this.streamSizeBytes = size;
|
|
245
|
-
|
|
246
|
-
this.openStream(filePath);
|
|
247
|
-
if (!this.stream) return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// 大小滚动
|
|
251
|
-
if (this.stream && this.maxFileBytes > 0 && this.streamSizeBytes + nextChunkBytes > this.maxFileBytes) {
|
|
252
|
-
await this.closeStream();
|
|
253
|
-
this.streamIndex = this.streamIndex + 1;
|
|
254
|
-
const filePath = this.getFilePath(date, this.streamIndex);
|
|
255
|
-
this.streamDate = date;
|
|
256
144
|
this.streamSizeBytes = 0;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (!this.stream) return;
|
|
260
|
-
}
|
|
145
|
+
st.end(() => resolve());
|
|
146
|
+
});
|
|
261
147
|
}
|
|
262
148
|
}
|
|
263
149
|
|
|
264
150
|
export async function flush() {
|
|
265
|
-
// 测试场景:mock logger 不需要 flush
|
|
266
151
|
if (mockInstance) return;
|
|
267
152
|
|
|
268
153
|
for (const sink of [appFileSink, errorFileSink]) {
|
|
269
|
-
if (
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
try {
|
|
154
|
+
if (sink) {
|
|
274
155
|
await sink.flushNow();
|
|
275
|
-
} catch {
|
|
276
|
-
// ignore
|
|
277
156
|
}
|
|
278
157
|
}
|
|
279
158
|
}
|
|
280
159
|
|
|
281
160
|
export async function shutdown() {
|
|
282
|
-
// 测试场景:mock logger 不需要 shutdown
|
|
283
161
|
if (mockInstance) return;
|
|
284
162
|
|
|
285
|
-
// 重要:shutdown 可能与后续 Logger 调用并发。
|
|
286
|
-
// 因此这里捕获“当前的旧 sink/instance 快照”,只关闭这些快照,避免把新创建的 sink 一并清掉。
|
|
287
163
|
const currentAppFileSink = appFileSink;
|
|
288
164
|
const currentErrorFileSink = errorFileSink;
|
|
289
165
|
|
|
290
166
|
for (const sink of [currentAppFileSink, currentErrorFileSink]) {
|
|
291
|
-
if (
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
try {
|
|
167
|
+
if (sink) {
|
|
296
168
|
await sink.shutdown();
|
|
297
|
-
} catch {
|
|
298
|
-
// ignore
|
|
299
169
|
}
|
|
300
170
|
}
|
|
301
171
|
|
|
@@ -305,23 +175,43 @@ export async function shutdown() {
|
|
|
305
175
|
if (errorFileSink === currentErrorFileSink) {
|
|
306
176
|
errorFileSink = null;
|
|
307
177
|
}
|
|
178
|
+
}
|
|
308
179
|
|
|
309
|
-
|
|
310
|
-
|
|
180
|
+
function getRuntimeLogDir(runtimeEnv) {
|
|
181
|
+
const cwd = process.cwd();
|
|
182
|
+
if (runtimeEnv === "development") {
|
|
183
|
+
return nodePathResolve(cwd, "logsDev");
|
|
184
|
+
}
|
|
185
|
+
return nodePathResolve(cwd, "logs");
|
|
311
186
|
}
|
|
312
187
|
|
|
313
|
-
function
|
|
188
|
+
function prepareLogDir(runtimeEnv, shouldClearDev = false) {
|
|
189
|
+
const logDir = getRuntimeLogDir(runtimeEnv);
|
|
190
|
+
const devLogDir = nodePathResolve(process.cwd(), "logsDev");
|
|
191
|
+
|
|
314
192
|
try {
|
|
315
|
-
if (!existsSync(
|
|
316
|
-
mkdirSync(
|
|
193
|
+
if (!existsSync(logDir)) {
|
|
194
|
+
mkdirSync(logDir, { recursive: true });
|
|
195
|
+
}
|
|
196
|
+
if (!existsSync(devLogDir)) {
|
|
197
|
+
mkdirSync(devLogDir, { recursive: true });
|
|
198
|
+
}
|
|
199
|
+
if (shouldClearDev && runtimeEnv === "development" && existsSync(devLogDir)) {
|
|
200
|
+
rmSync(devLogDir, { recursive: true, force: true });
|
|
201
|
+
mkdirSync(devLogDir, { recursive: true });
|
|
317
202
|
}
|
|
318
203
|
} catch (error) {
|
|
319
|
-
|
|
320
|
-
throw new Error(`创建 logs 目录失败: ${RUNTIME_LOG_DIR}. ${error?.message || error}`, {
|
|
204
|
+
throw new Error(`创建日志目录失败: ${logDir}. ${error?.message || error}`, {
|
|
321
205
|
cause: error,
|
|
322
206
|
code: "runtime"
|
|
323
207
|
});
|
|
324
208
|
}
|
|
209
|
+
|
|
210
|
+
return logDir;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function ensureLogDirExists() {
|
|
214
|
+
config.dir = prepareLogDir(config.runtimeEnv);
|
|
325
215
|
}
|
|
326
216
|
|
|
327
217
|
// 方案B:删除“启动时清理旧日志”功能(减少 I/O 与复杂度)。
|
|
@@ -330,40 +220,23 @@ function ensureLogDirExists() {
|
|
|
330
220
|
* 配置日志
|
|
331
221
|
*/
|
|
332
222
|
export function configure(cfg) {
|
|
333
|
-
// 旧实例可能仍持有文件句柄;这里异步关闭(不阻塞主流程)
|
|
334
223
|
shutdown();
|
|
335
224
|
|
|
336
|
-
// 方案B:每次 configure 都从默认配置重新构建(避免继承上一次配置造成测试/运行时污染)
|
|
337
225
|
config = Object.assign(
|
|
338
226
|
{
|
|
339
227
|
debug: 0,
|
|
340
|
-
dir:
|
|
341
|
-
maxSize: 20,
|
|
228
|
+
dir: "",
|
|
342
229
|
runtimeEnv: "production"
|
|
343
230
|
},
|
|
344
231
|
cfg
|
|
345
232
|
);
|
|
346
233
|
|
|
347
|
-
|
|
348
|
-
config.
|
|
349
|
-
|
|
350
|
-
if (cfg && !isString(cfg.runtimeEnv) && isString(cfg.runMode)) {
|
|
351
|
-
config.runtimeEnv = cfg.runMode;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// maxSize:仅按 MB 计算,且强制范围 10..100
|
|
355
|
-
{
|
|
356
|
-
const raw = config.maxSize;
|
|
357
|
-
let mb = isFiniteNumber(raw) ? raw : 20;
|
|
358
|
-
if (mb < 10) mb = 10;
|
|
359
|
-
if (mb > 100) mb = 100;
|
|
360
|
-
config.maxSize = mb;
|
|
361
|
-
}
|
|
234
|
+
config.dir = getRuntimeLogDir(config.runtimeEnv);
|
|
235
|
+
prepareLogDir(config.runtimeEnv, true);
|
|
362
236
|
|
|
363
237
|
appFileSink = null;
|
|
364
238
|
errorFileSink = null;
|
|
365
239
|
|
|
366
|
-
// 运行时清洗上限:只保留深度/节点/对象键数量保护。
|
|
367
240
|
sanitizeOptions = {
|
|
368
241
|
sanitizeDepth: normalizePositiveInt(config.sanitizeDepth, 5, 1, 10),
|
|
369
242
|
sanitizeNodes: normalizePositiveInt(config.sanitizeNodes, 5000, 50, 20000),
|
|
@@ -381,87 +254,56 @@ export function setMockLogger(mock) {
|
|
|
381
254
|
}
|
|
382
255
|
|
|
383
256
|
function buildJsonLine(level, timeMs, record) {
|
|
384
|
-
const
|
|
257
|
+
const out = {
|
|
385
258
|
level: level,
|
|
386
259
|
time: formatYmdHms(timeMs),
|
|
387
260
|
pid: process.pid
|
|
388
261
|
};
|
|
389
262
|
|
|
390
|
-
// 目标:让 level/time/timeFormat/pid/hostname 在 JSON 行首出现(更易读),同时保持 JSONL 可解析。
|
|
391
|
-
// 约束:record 不允许覆盖基础字段。
|
|
392
|
-
// 实现:先写入基础字段,再按 record 的 key 顺序追加其它字段。
|
|
393
263
|
if (record && isPlainObject(record)) {
|
|
394
|
-
const out = {};
|
|
395
|
-
out["level"] = base["level"];
|
|
396
|
-
out["time"] = base["time"];
|
|
397
|
-
out["pid"] = base["pid"];
|
|
398
|
-
|
|
399
264
|
for (const key of Object.keys(record)) {
|
|
400
|
-
if (key === "level")
|
|
401
|
-
|
|
402
|
-
|
|
265
|
+
if (key === "level" || key === "time" || key === "pid") {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
403
268
|
out[key] = record[key];
|
|
404
269
|
}
|
|
405
270
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
out["msg"] = record.msg;
|
|
409
|
-
} else if (out["msg"] === undefined) {
|
|
410
|
-
out["msg"] = "";
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
try {
|
|
414
|
-
return `${JSON.stringify(out)}\n`;
|
|
415
|
-
} catch {
|
|
416
|
-
try {
|
|
417
|
-
return `${JSON.stringify({ level: out["level"], time: out["time"], pid: out["pid"], msg: "[Unserializable log record]" })}\n`;
|
|
418
|
-
} catch {
|
|
419
|
-
return '{"msg":"[Unserializable log record]"}\n';
|
|
420
|
-
}
|
|
271
|
+
if (out.msg === undefined) {
|
|
272
|
+
out.msg = "";
|
|
421
273
|
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (base["msg"] === undefined) {
|
|
425
|
-
base["msg"] = "";
|
|
274
|
+
} else {
|
|
275
|
+
out.msg = "";
|
|
426
276
|
}
|
|
427
277
|
|
|
428
278
|
try {
|
|
429
|
-
return `${JSON.stringify(
|
|
279
|
+
return `${JSON.stringify(out)}\n`;
|
|
430
280
|
} catch {
|
|
431
|
-
|
|
432
|
-
return `${JSON.stringify({ level: base["level"], time: base["time"], pid: base["pid"], msg: "[Unserializable log record]" })}\n`;
|
|
433
|
-
} catch {
|
|
434
|
-
return '{"msg":"[Unserializable log record]"}\n';
|
|
435
|
-
}
|
|
281
|
+
return `${JSON.stringify({ level: out.level, time: out.time, pid: out.pid, msg: "[Unserializable log record]" })}\n`;
|
|
436
282
|
}
|
|
437
283
|
}
|
|
438
284
|
|
|
439
285
|
function ensureSinksReady(kind) {
|
|
440
286
|
ensureLogDirExists();
|
|
441
287
|
|
|
442
|
-
const maxSizeMb = isFiniteNumber(config.maxSize) ? config.maxSize : 20;
|
|
443
|
-
const maxFileBytes = Math.floor(maxSizeMb * 1024 * 1024);
|
|
444
|
-
|
|
445
288
|
if (kind === "app") {
|
|
446
289
|
if (!appFileSink) {
|
|
447
|
-
appFileSink = new LogFileSink(
|
|
290
|
+
appFileSink = new LogFileSink("app");
|
|
448
291
|
}
|
|
449
|
-
return
|
|
292
|
+
return appFileSink;
|
|
450
293
|
}
|
|
451
294
|
|
|
452
295
|
if (!errorFileSink) {
|
|
453
|
-
errorFileSink = new LogFileSink(
|
|
296
|
+
errorFileSink = new LogFileSink("error");
|
|
454
297
|
}
|
|
455
|
-
|
|
456
|
-
return { fileSink: errorFileSink };
|
|
298
|
+
return errorFileSink;
|
|
457
299
|
}
|
|
458
300
|
|
|
459
301
|
function writeJsonl(kind, level, record) {
|
|
460
|
-
const
|
|
302
|
+
const sink = ensureSinksReady(kind);
|
|
461
303
|
const time = Date.now();
|
|
462
304
|
const sanitizedRecord = sanitizeLogObject(record, sanitizeOptions);
|
|
463
305
|
const fileLine = buildJsonLine(level, time, sanitizedRecord);
|
|
464
|
-
|
|
306
|
+
sink.write(fileLine);
|
|
465
307
|
}
|
|
466
308
|
|
|
467
309
|
// 对象清洗/脱敏逻辑已下沉到 utils/loggerUtils.js(减少 logger.js 复杂度)。
|
|
@@ -507,15 +349,7 @@ function logWrite(level, input) {
|
|
|
507
349
|
// 测试场景:mock logger 走同步写入,并在入口进行清洗/脱敏控制
|
|
508
350
|
if (mockInstance) {
|
|
509
351
|
const sanitized = sanitizeLogObject(record0, sanitizeOptions);
|
|
510
|
-
|
|
511
|
-
mockInstance.info(sanitized);
|
|
512
|
-
} else if (level === "warn") {
|
|
513
|
-
mockInstance.warn(sanitized);
|
|
514
|
-
} else if (level === "error") {
|
|
515
|
-
mockInstance.error(sanitized);
|
|
516
|
-
} else {
|
|
517
|
-
mockInstance.debug(sanitized);
|
|
518
|
-
}
|
|
352
|
+
mockInstance[level](sanitized);
|
|
519
353
|
return;
|
|
520
354
|
}
|
|
521
355
|
|
|
@@ -527,26 +361,23 @@ function logWrite(level, input) {
|
|
|
527
361
|
}
|
|
528
362
|
}
|
|
529
363
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
*/
|
|
533
|
-
export const Logger = {
|
|
534
|
-
info: function (msg, data) {
|
|
364
|
+
function buildLogMethod(level) {
|
|
365
|
+
return function (msg, data) {
|
|
535
366
|
if (isPlainObject(msg)) {
|
|
536
|
-
logWrite(
|
|
367
|
+
logWrite(level, msg);
|
|
537
368
|
return;
|
|
538
369
|
}
|
|
539
370
|
|
|
540
|
-
logWrite(
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (isPlainObject(msg)) {
|
|
544
|
-
logWrite("warn", msg);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
371
|
+
logWrite(level, { msg: msg, data: data });
|
|
372
|
+
};
|
|
373
|
+
}
|
|
547
374
|
|
|
548
|
-
|
|
549
|
-
|
|
375
|
+
/**
|
|
376
|
+
* 日志实例(延迟初始化)
|
|
377
|
+
*/
|
|
378
|
+
export const Logger = {
|
|
379
|
+
info: buildLogMethod("info"),
|
|
380
|
+
warn: buildLogMethod("warn"),
|
|
550
381
|
error: function (msg, err, data) {
|
|
551
382
|
if (isPlainObject(msg)) {
|
|
552
383
|
logWrite("error", msg);
|
|
@@ -555,14 +386,7 @@ export const Logger = {
|
|
|
555
386
|
|
|
556
387
|
logWrite("error", { msg: msg, err: err, data: data });
|
|
557
388
|
},
|
|
558
|
-
debug:
|
|
559
|
-
if (isPlainObject(msg)) {
|
|
560
|
-
logWrite("debug", msg);
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
logWrite("debug", { msg: msg, data: data });
|
|
565
|
-
},
|
|
389
|
+
debug: buildLogMethod("debug"),
|
|
566
390
|
flush: async function () {
|
|
567
391
|
await flush();
|
|
568
392
|
},
|
package/package.json
CHANGED
package/plugins/logger.js
CHANGED
|
@@ -10,11 +10,8 @@ import { Logger } from "../lib/logger.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export default {
|
|
12
12
|
order: 2,
|
|
13
|
-
handler: async function (
|
|
14
|
-
//
|
|
15
|
-
if (befly.config && befly.config.logger) {
|
|
16
|
-
Logger.configure(befly.config.logger);
|
|
17
|
-
}
|
|
13
|
+
handler: async function () {
|
|
14
|
+
// 日志已在 createBefly 中按 runMode 初始化,这里只注入 Logger 实例
|
|
18
15
|
return Logger;
|
|
19
16
|
}
|
|
20
17
|
};
|
package/utils/loggerUtils.js
CHANGED