befly 3.61.0 → 3.63.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/apis/auth/login.js +65 -13
- package/apis/tongJi/_tongJi.js +92 -41
- package/apis/tongJi/dailyReport.js +73 -51
- package/apis/tongJi/dailyStats.js +98 -125
- package/apis/tongJi/dailyStatsDistribution.js +86 -0
- package/apis/tongJi/errorList.js +5 -9
- package/apis/tongJi/errorReport.js +81 -44
- package/apis/tongJi/errorStats.js +19 -125
- package/checks/config.js +1 -4
- package/configs/beflyConfig.json +1 -4
- package/index.js +5 -0
- package/lib/logger.js +125 -296
- package/package.json +1 -1
- package/plugins/cron.js +96 -0
- package/plugins/logger.js +2 -5
- package/sql/befly.sql +53 -25
- package/tables/dailyReport.json +124 -0
- package/tables/errorReport.json +31 -48
- package/tables/loginLog.json +29 -32
- package/utils/is.js +3 -36
- package/utils/loggerUtils.js +0 -3
- package/utils/userAgent.js +229 -104
- package/apis/tongJi/_daily.js +0 -159
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,87 @@ 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();
|
|
68
|
+
return this.closeStream();
|
|
111
69
|
}
|
|
112
70
|
|
|
113
|
-
|
|
114
|
-
if (this.
|
|
71
|
+
getFilePath(date, index) {
|
|
72
|
+
if (this.prefix === "debug") {
|
|
73
|
+
return nodePathJoin(config.dir, "dev.log");
|
|
74
|
+
}
|
|
115
75
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
this.flush();
|
|
120
|
-
}, this.flushDelayMs);
|
|
76
|
+
const suffix = index > 0 ? `.${index}` : "";
|
|
77
|
+
const filename = this.prefix === "app" ? `${date}${suffix}.log` : `${this.prefix}.${date}${suffix}.log`;
|
|
78
|
+
return nodePathJoin(config.dir, filename);
|
|
121
79
|
}
|
|
122
80
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
clearTimeout(this.scheduledTimer);
|
|
126
|
-
this.scheduledTimer = null;
|
|
127
|
-
}
|
|
81
|
+
ensureStreamReady(nextChunkBytes) {
|
|
82
|
+
const date = formatYmdHms(Date.now(), "date");
|
|
128
83
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
84
|
+
if (this.stream && this.streamDate && date !== this.streamDate) {
|
|
85
|
+
this.closeStreamSync();
|
|
86
|
+
}
|
|
132
87
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
}
|
|
88
|
+
if (!this.stream) {
|
|
89
|
+
this.streamDate = date;
|
|
90
|
+
this.streamIndex = 0;
|
|
91
|
+
this.streamSizeBytes = this.getFileSize(this.getFilePath(date, 0));
|
|
92
|
+
|
|
93
|
+
this.openStream(this.getFilePath(date, 0));
|
|
151
94
|
}
|
|
152
|
-
}
|
|
153
95
|
|
|
154
|
-
|
|
155
|
-
|
|
96
|
+
if (this.prefix === "debug") {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
156
99
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
100
|
+
if (this.stream && this.streamSizeBytes + nextChunkBytes > MAX_FILE_BYTES) {
|
|
101
|
+
this.closeStreamSync();
|
|
102
|
+
this.streamIndex += 1;
|
|
103
|
+
this.streamSizeBytes = 0;
|
|
163
104
|
|
|
164
|
-
|
|
165
|
-
if (!ok) {
|
|
166
|
-
await new Promise((resolve) => {
|
|
167
|
-
const stream = this.stream;
|
|
168
|
-
if (!stream) {
|
|
169
|
-
resolve();
|
|
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;
|
|
105
|
+
this.openStream(this.getFilePath(date, this.streamIndex));
|
|
182
106
|
}
|
|
183
107
|
}
|
|
184
108
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
st.end(() => resolve());
|
|
193
|
-
} catch {
|
|
194
|
-
resolve();
|
|
195
|
-
}
|
|
196
|
-
});
|
|
109
|
+
getFileSize(filePath) {
|
|
110
|
+
try {
|
|
111
|
+
return statSync(filePath).size;
|
|
112
|
+
} catch {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
197
115
|
}
|
|
198
116
|
|
|
199
117
|
openStream(filePath) {
|
|
@@ -202,7 +120,7 @@ class LogFileSink {
|
|
|
202
120
|
this.stream.on("error", (error) => {
|
|
203
121
|
safeWriteStderr(`[Logger] file sink error (${this.prefix}): ${error?.message || error}`);
|
|
204
122
|
this.disabled = true;
|
|
205
|
-
this.
|
|
123
|
+
this.closeStreamSync();
|
|
206
124
|
});
|
|
207
125
|
} catch (error) {
|
|
208
126
|
safeWriteStderr(`[Logger] createWriteStream failed (${this.prefix}): ${error?.message || error}`);
|
|
@@ -211,91 +129,51 @@ class LogFileSink {
|
|
|
211
129
|
}
|
|
212
130
|
}
|
|
213
131
|
|
|
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;
|
|
132
|
+
closeStreamSync() {
|
|
133
|
+
if (this.stream) {
|
|
134
|
+
this.stream.end();
|
|
229
135
|
}
|
|
136
|
+
this.stream = null;
|
|
137
|
+
this.streamDate = "";
|
|
138
|
+
this.streamIndex = 0;
|
|
139
|
+
this.streamSizeBytes = 0;
|
|
140
|
+
}
|
|
230
141
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
size = isNumber(st.size) ? st.size : 0;
|
|
238
|
-
} catch {
|
|
239
|
-
size = 0;
|
|
142
|
+
closeStream() {
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
const st = this.stream;
|
|
145
|
+
if (!st) {
|
|
146
|
+
resolve();
|
|
147
|
+
return;
|
|
240
148
|
}
|
|
241
|
-
|
|
242
|
-
this.streamDate =
|
|
149
|
+
this.stream = null;
|
|
150
|
+
this.streamDate = "";
|
|
243
151
|
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
152
|
this.streamSizeBytes = 0;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (!this.stream) return;
|
|
260
|
-
}
|
|
153
|
+
st.end(() => resolve());
|
|
154
|
+
});
|
|
261
155
|
}
|
|
262
156
|
}
|
|
263
157
|
|
|
264
158
|
export async function flush() {
|
|
265
|
-
// 测试场景:mock logger 不需要 flush
|
|
266
159
|
if (mockInstance) return;
|
|
267
160
|
|
|
268
161
|
for (const sink of [appFileSink, errorFileSink]) {
|
|
269
|
-
if (
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
try {
|
|
162
|
+
if (sink) {
|
|
274
163
|
await sink.flushNow();
|
|
275
|
-
} catch {
|
|
276
|
-
// ignore
|
|
277
164
|
}
|
|
278
165
|
}
|
|
279
166
|
}
|
|
280
167
|
|
|
281
168
|
export async function shutdown() {
|
|
282
|
-
// 测试场景:mock logger 不需要 shutdown
|
|
283
169
|
if (mockInstance) return;
|
|
284
170
|
|
|
285
|
-
// 重要:shutdown 可能与后续 Logger 调用并发。
|
|
286
|
-
// 因此这里捕获“当前的旧 sink/instance 快照”,只关闭这些快照,避免把新创建的 sink 一并清掉。
|
|
287
171
|
const currentAppFileSink = appFileSink;
|
|
288
172
|
const currentErrorFileSink = errorFileSink;
|
|
289
173
|
|
|
290
174
|
for (const sink of [currentAppFileSink, currentErrorFileSink]) {
|
|
291
|
-
if (
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
try {
|
|
175
|
+
if (sink) {
|
|
296
176
|
await sink.shutdown();
|
|
297
|
-
} catch {
|
|
298
|
-
// ignore
|
|
299
177
|
}
|
|
300
178
|
}
|
|
301
179
|
|
|
@@ -305,65 +183,58 @@ export async function shutdown() {
|
|
|
305
183
|
if (errorFileSink === currentErrorFileSink) {
|
|
306
184
|
errorFileSink = null;
|
|
307
185
|
}
|
|
186
|
+
}
|
|
308
187
|
|
|
309
|
-
|
|
310
|
-
|
|
188
|
+
function getRuntimeLogDir() {
|
|
189
|
+
return nodePathResolve(process.cwd(), "logs");
|
|
311
190
|
}
|
|
312
191
|
|
|
313
|
-
function
|
|
192
|
+
function prepareLogDir(runtimeEnv, shouldClearDev = false) {
|
|
193
|
+
const logDir = getRuntimeLogDir(runtimeEnv);
|
|
194
|
+
const devLogPath = nodePathResolve(logDir, "dev.log");
|
|
195
|
+
|
|
314
196
|
try {
|
|
315
|
-
if (!existsSync(
|
|
316
|
-
mkdirSync(
|
|
197
|
+
if (!existsSync(logDir)) {
|
|
198
|
+
mkdirSync(logDir, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
if (shouldClearDev && runtimeEnv === "development" && existsSync(devLogPath)) {
|
|
201
|
+
rmSync(devLogPath, { force: 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;
|
|
325
211
|
}
|
|
326
212
|
|
|
327
|
-
|
|
213
|
+
function ensureLogDirExists() {
|
|
214
|
+
config.dir = prepareLogDir(config.runtimeEnv);
|
|
215
|
+
}
|
|
328
216
|
|
|
329
217
|
/**
|
|
330
218
|
* 配置日志
|
|
331
219
|
*/
|
|
332
220
|
export function configure(cfg) {
|
|
333
|
-
// 旧实例可能仍持有文件句柄;这里异步关闭(不阻塞主流程)
|
|
334
221
|
shutdown();
|
|
335
222
|
|
|
336
|
-
// 方案B:每次 configure 都从默认配置重新构建(避免继承上一次配置造成测试/运行时污染)
|
|
337
223
|
config = Object.assign(
|
|
338
224
|
{
|
|
339
225
|
debug: 0,
|
|
340
|
-
dir:
|
|
341
|
-
maxSize: 20,
|
|
226
|
+
dir: "",
|
|
342
227
|
runtimeEnv: "production"
|
|
343
228
|
},
|
|
344
229
|
cfg
|
|
345
230
|
);
|
|
346
231
|
|
|
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
|
-
}
|
|
232
|
+
config.dir = getRuntimeLogDir(config.runtimeEnv);
|
|
233
|
+
prepareLogDir(config.runtimeEnv, true);
|
|
362
234
|
|
|
363
235
|
appFileSink = null;
|
|
364
236
|
errorFileSink = null;
|
|
365
237
|
|
|
366
|
-
// 运行时清洗上限:只保留深度/节点/对象键数量保护。
|
|
367
238
|
sanitizeOptions = {
|
|
368
239
|
sanitizeDepth: normalizePositiveInt(config.sanitizeDepth, 5, 1, 10),
|
|
369
240
|
sanitizeNodes: normalizePositiveInt(config.sanitizeNodes, 5000, 50, 20000),
|
|
@@ -381,87 +252,63 @@ export function setMockLogger(mock) {
|
|
|
381
252
|
}
|
|
382
253
|
|
|
383
254
|
function buildJsonLine(level, timeMs, record) {
|
|
384
|
-
const
|
|
255
|
+
const out = {
|
|
385
256
|
level: level,
|
|
386
257
|
time: formatYmdHms(timeMs),
|
|
387
258
|
pid: process.pid
|
|
388
259
|
};
|
|
389
260
|
|
|
390
|
-
// 目标:让 level/time/timeFormat/pid/hostname 在 JSON 行首出现(更易读),同时保持 JSONL 可解析。
|
|
391
|
-
// 约束:record 不允许覆盖基础字段。
|
|
392
|
-
// 实现:先写入基础字段,再按 record 的 key 顺序追加其它字段。
|
|
393
261
|
if (record && isPlainObject(record)) {
|
|
394
|
-
const out = {};
|
|
395
|
-
out["level"] = base["level"];
|
|
396
|
-
out["time"] = base["time"];
|
|
397
|
-
out["pid"] = base["pid"];
|
|
398
|
-
|
|
399
262
|
for (const key of Object.keys(record)) {
|
|
400
|
-
if (key === "level")
|
|
401
|
-
|
|
402
|
-
|
|
263
|
+
if (key === "level" || key === "time" || key === "pid") {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
403
266
|
out[key] = record[key];
|
|
404
267
|
}
|
|
405
268
|
|
|
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
|
-
}
|
|
269
|
+
if (out.msg === undefined) {
|
|
270
|
+
out.msg = "";
|
|
421
271
|
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (base["msg"] === undefined) {
|
|
425
|
-
base["msg"] = "";
|
|
272
|
+
} else {
|
|
273
|
+
out.msg = "";
|
|
426
274
|
}
|
|
427
275
|
|
|
428
276
|
try {
|
|
429
|
-
return `${JSON.stringify(
|
|
277
|
+
return `${JSON.stringify(out)}\n`;
|
|
430
278
|
} 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
|
-
}
|
|
279
|
+
return `${JSON.stringify({ level: out.level, time: out.time, pid: out.pid, msg: "[Unserializable log record]" })}\n`;
|
|
436
280
|
}
|
|
437
281
|
}
|
|
438
282
|
|
|
439
283
|
function ensureSinksReady(kind) {
|
|
440
284
|
ensureLogDirExists();
|
|
441
285
|
|
|
442
|
-
|
|
443
|
-
|
|
286
|
+
if (config.runtimeEnv === "development") {
|
|
287
|
+
if (!appFileSink) {
|
|
288
|
+
appFileSink = new LogFileSink("debug");
|
|
289
|
+
}
|
|
290
|
+
return appFileSink;
|
|
291
|
+
}
|
|
444
292
|
|
|
445
293
|
if (kind === "app") {
|
|
446
294
|
if (!appFileSink) {
|
|
447
|
-
appFileSink = new LogFileSink(
|
|
295
|
+
appFileSink = new LogFileSink("app");
|
|
448
296
|
}
|
|
449
|
-
return
|
|
297
|
+
return appFileSink;
|
|
450
298
|
}
|
|
451
299
|
|
|
452
300
|
if (!errorFileSink) {
|
|
453
|
-
errorFileSink = new LogFileSink(
|
|
301
|
+
errorFileSink = new LogFileSink("error");
|
|
454
302
|
}
|
|
455
|
-
|
|
456
|
-
return { fileSink: errorFileSink };
|
|
303
|
+
return errorFileSink;
|
|
457
304
|
}
|
|
458
305
|
|
|
459
306
|
function writeJsonl(kind, level, record) {
|
|
460
|
-
const
|
|
307
|
+
const sink = ensureSinksReady(kind);
|
|
461
308
|
const time = Date.now();
|
|
462
309
|
const sanitizedRecord = sanitizeLogObject(record, sanitizeOptions);
|
|
463
310
|
const fileLine = buildJsonLine(level, time, sanitizedRecord);
|
|
464
|
-
|
|
311
|
+
sink.write(fileLine);
|
|
465
312
|
}
|
|
466
313
|
|
|
467
314
|
// 对象清洗/脱敏逻辑已下沉到 utils/loggerUtils.js(减少 logger.js 复杂度)。
|
|
@@ -507,15 +354,7 @@ function logWrite(level, input) {
|
|
|
507
354
|
// 测试场景:mock logger 走同步写入,并在入口进行清洗/脱敏控制
|
|
508
355
|
if (mockInstance) {
|
|
509
356
|
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
|
-
}
|
|
357
|
+
mockInstance[level](sanitized);
|
|
519
358
|
return;
|
|
520
359
|
}
|
|
521
360
|
|
|
@@ -527,26 +366,23 @@ function logWrite(level, input) {
|
|
|
527
366
|
}
|
|
528
367
|
}
|
|
529
368
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
*/
|
|
533
|
-
export const Logger = {
|
|
534
|
-
info: function (msg, data) {
|
|
369
|
+
function buildLogMethod(level) {
|
|
370
|
+
return function (msg, data) {
|
|
535
371
|
if (isPlainObject(msg)) {
|
|
536
|
-
logWrite(
|
|
372
|
+
logWrite(level, msg);
|
|
537
373
|
return;
|
|
538
374
|
}
|
|
539
375
|
|
|
540
|
-
logWrite(
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (isPlainObject(msg)) {
|
|
544
|
-
logWrite("warn", msg);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
376
|
+
logWrite(level, { msg: msg, data: data });
|
|
377
|
+
};
|
|
378
|
+
}
|
|
547
379
|
|
|
548
|
-
|
|
549
|
-
|
|
380
|
+
/**
|
|
381
|
+
* 日志实例(延迟初始化)
|
|
382
|
+
*/
|
|
383
|
+
export const Logger = {
|
|
384
|
+
info: buildLogMethod("info"),
|
|
385
|
+
warn: buildLogMethod("warn"),
|
|
550
386
|
error: function (msg, err, data) {
|
|
551
387
|
if (isPlainObject(msg)) {
|
|
552
388
|
logWrite("error", msg);
|
|
@@ -555,14 +391,7 @@ export const Logger = {
|
|
|
555
391
|
|
|
556
392
|
logWrite("error", { msg: msg, err: err, data: data });
|
|
557
393
|
},
|
|
558
|
-
debug:
|
|
559
|
-
if (isPlainObject(msg)) {
|
|
560
|
-
logWrite("debug", msg);
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
logWrite("debug", { msg: msg, data: data });
|
|
565
|
-
},
|
|
394
|
+
debug: buildLogMethod("debug"),
|
|
566
395
|
flush: async function () {
|
|
567
396
|
await flush();
|
|
568
397
|
},
|