befly 3.16.10 → 3.16.11
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/README.md +0 -129
- package/befly.js +12769 -0
- package/befly.min.js +47 -0
- package/package.json +18 -29
- package/dist/befly.config.d.ts +0 -7
- package/dist/befly.config.js +0 -128
- package/dist/befly.js +0 -17348
- package/dist/befly.min.js +0 -23
- package/dist/checks/checkApi.d.ts +0 -1
- package/dist/checks/checkApi.js +0 -139
- package/dist/checks/checkConfig.d.ts +0 -9
- package/dist/checks/checkConfig.js +0 -255
- package/dist/checks/checkHook.d.ts +0 -1
- package/dist/checks/checkHook.js +0 -132
- package/dist/checks/checkMenu.d.ts +0 -3
- package/dist/checks/checkMenu.js +0 -106
- package/dist/checks/checkPlugin.d.ts +0 -1
- package/dist/checks/checkPlugin.js +0 -132
- package/dist/checks/checkTable.d.ts +0 -7
- package/dist/checks/checkTable.js +0 -431
- package/dist/configs/presetRegexp.d.ts +0 -145
- package/dist/configs/presetRegexp.js +0 -218
- package/dist/hooks/auth.d.ts +0 -3
- package/dist/hooks/auth.js +0 -24
- package/dist/hooks/cors.d.ts +0 -7
- package/dist/hooks/cors.js +0 -36
- package/dist/hooks/parser.d.ts +0 -10
- package/dist/hooks/parser.js +0 -76
- package/dist/hooks/permission.d.ts +0 -11
- package/dist/hooks/permission.js +0 -78
- package/dist/hooks/validator.d.ts +0 -7
- package/dist/hooks/validator.js +0 -52
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -316
- package/dist/lib/asyncContext.d.ts +0 -21
- package/dist/lib/asyncContext.js +0 -27
- package/dist/lib/cacheHelper.d.ts +0 -128
- package/dist/lib/cacheHelper.js +0 -477
- package/dist/lib/cacheKeys.d.ts +0 -27
- package/dist/lib/cacheKeys.js +0 -37
- package/dist/lib/cipher.d.ts +0 -153
- package/dist/lib/cipher.js +0 -237
- package/dist/lib/connect.d.ts +0 -95
- package/dist/lib/connect.js +0 -313
- package/dist/lib/dbHelper.d.ts +0 -229
- package/dist/lib/dbHelper.js +0 -1099
- package/dist/lib/dbUtils.d.ts +0 -91
- package/dist/lib/dbUtils.js +0 -544
- package/dist/lib/jwt.d.ts +0 -13
- package/dist/lib/jwt.js +0 -77
- package/dist/lib/logger.d.ts +0 -46
- package/dist/lib/logger.js +0 -731
- package/dist/lib/redisHelper.d.ts +0 -193
- package/dist/lib/redisHelper.js +0 -598
- package/dist/lib/sqlBuilder.d.ts +0 -160
- package/dist/lib/sqlBuilder.js +0 -837
- package/dist/lib/sqlCheck.d.ts +0 -23
- package/dist/lib/sqlCheck.js +0 -119
- package/dist/lib/validator.d.ts +0 -45
- package/dist/lib/validator.js +0 -424
- package/dist/loader/loadApis.d.ts +0 -12
- package/dist/loader/loadApis.js +0 -71
- package/dist/loader/loadHooks.d.ts +0 -7
- package/dist/loader/loadHooks.js +0 -50
- package/dist/loader/loadPlugins.d.ts +0 -8
- package/dist/loader/loadPlugins.js +0 -69
- package/dist/paths.d.ts +0 -93
- package/dist/paths.js +0 -100
- package/dist/plugins/cache.d.ts +0 -10
- package/dist/plugins/cache.js +0 -24
- package/dist/plugins/cipher.d.ts +0 -7
- package/dist/plugins/cipher.js +0 -14
- package/dist/plugins/config.d.ts +0 -3
- package/dist/plugins/config.js +0 -9
- package/dist/plugins/db.d.ts +0 -10
- package/dist/plugins/db.js +0 -48
- package/dist/plugins/jwt.d.ts +0 -6
- package/dist/plugins/jwt.js +0 -13
- package/dist/plugins/logger.d.ts +0 -10
- package/dist/plugins/logger.js +0 -21
- package/dist/plugins/redis.d.ts +0 -10
- package/dist/plugins/redis.js +0 -40
- package/dist/plugins/tool.d.ts +0 -75
- package/dist/plugins/tool.js +0 -105
- package/dist/router/api.d.ts +0 -14
- package/dist/router/api.js +0 -109
- package/dist/router/static.d.ts +0 -9
- package/dist/router/static.js +0 -56
- package/dist/scripts/ensureDist.d.ts +0 -1
- package/dist/scripts/ensureDist.js +0 -296
- package/dist/sync/syncApi.d.ts +0 -3
- package/dist/sync/syncApi.js +0 -163
- package/dist/sync/syncCache.d.ts +0 -2
- package/dist/sync/syncCache.js +0 -14
- package/dist/sync/syncDev.d.ts +0 -6
- package/dist/sync/syncDev.js +0 -166
- package/dist/sync/syncMenu.d.ts +0 -14
- package/dist/sync/syncMenu.js +0 -308
- package/dist/sync/syncTable.d.ts +0 -126
- package/dist/sync/syncTable.js +0 -1129
- package/dist/types/api.d.ts +0 -177
- package/dist/types/api.js +0 -4
- package/dist/types/befly.d.ts +0 -231
- package/dist/types/befly.js +0 -4
- package/dist/types/cache.d.ts +0 -96
- package/dist/types/cache.js +0 -4
- package/dist/types/cipher.d.ts +0 -27
- package/dist/types/cipher.js +0 -7
- package/dist/types/common.d.ts +0 -127
- package/dist/types/common.js +0 -5
- package/dist/types/context.d.ts +0 -39
- package/dist/types/context.js +0 -4
- package/dist/types/coreError.d.ts +0 -31
- package/dist/types/coreError.js +0 -38
- package/dist/types/crypto.d.ts +0 -20
- package/dist/types/crypto.js +0 -4
- package/dist/types/database.d.ts +0 -182
- package/dist/types/database.js +0 -4
- package/dist/types/hook.d.ts +0 -30
- package/dist/types/hook.js +0 -19
- package/dist/types/jwt.d.ts +0 -76
- package/dist/types/jwt.js +0 -4
- package/dist/types/logger.d.ts +0 -95
- package/dist/types/logger.js +0 -6
- package/dist/types/plugin.d.ts +0 -27
- package/dist/types/plugin.js +0 -17
- package/dist/types/redis.d.ts +0 -80
- package/dist/types/redis.js +0 -4
- package/dist/types/roleApisCache.d.ts +0 -21
- package/dist/types/roleApisCache.js +0 -8
- package/dist/types/sync.d.ts +0 -93
- package/dist/types/sync.js +0 -4
- package/dist/types/table.d.ts +0 -34
- package/dist/types/table.js +0 -4
- package/dist/types/validate.d.ts +0 -113
- package/dist/types/validate.js +0 -4
- package/dist/utils/calcPerfTime.d.ts +0 -4
- package/dist/utils/calcPerfTime.js +0 -13
- package/dist/utils/cors.d.ts +0 -8
- package/dist/utils/cors.js +0 -17
- package/dist/utils/dbFieldRules.d.ts +0 -31
- package/dist/utils/dbFieldRules.js +0 -94
- package/dist/utils/fieldClear.d.ts +0 -11
- package/dist/utils/fieldClear.js +0 -57
- package/dist/utils/formatYmdHms.d.ts +0 -1
- package/dist/utils/formatYmdHms.js +0 -20
- package/dist/utils/getClientIp.d.ts +0 -6
- package/dist/utils/getClientIp.js +0 -39
- package/dist/utils/importDefault.d.ts +0 -1
- package/dist/utils/importDefault.js +0 -53
- package/dist/utils/isDirentDirectory.d.ts +0 -3
- package/dist/utils/isDirentDirectory.js +0 -18
- package/dist/utils/loadMenuConfigs.d.ts +0 -11
- package/dist/utils/loadMenuConfigs.js +0 -130
- package/dist/utils/loggerUtils.d.ts +0 -18
- package/dist/utils/loggerUtils.js +0 -171
- package/dist/utils/mergeAndConcat.d.ts +0 -7
- package/dist/utils/mergeAndConcat.js +0 -77
- package/dist/utils/normalizeFieldDefinition.d.ts +0 -18
- package/dist/utils/normalizeFieldDefinition.js +0 -27
- package/dist/utils/processInfo.d.ts +0 -26
- package/dist/utils/processInfo.js +0 -41
- package/dist/utils/response.d.ts +0 -20
- package/dist/utils/response.js +0 -96
- package/dist/utils/scanAddons.d.ts +0 -15
- package/dist/utils/scanAddons.js +0 -35
- package/dist/utils/scanCoreBuiltins.d.ts +0 -3
- package/dist/utils/scanCoreBuiltins.js +0 -72
- package/dist/utils/scanFiles.d.ts +0 -32
- package/dist/utils/scanFiles.js +0 -124
- package/dist/utils/scanSources.d.ts +0 -10
- package/dist/utils/scanSources.js +0 -46
- package/dist/utils/sortModules.d.ts +0 -28
- package/dist/utils/sortModules.js +0 -105
- package/dist/utils/sqlUtil.d.ts +0 -33
- package/dist/utils/sqlUtil.js +0 -146
- package/dist/utils/util.d.ts +0 -172
- package/dist/utils/util.js +0 -517
package/dist/lib/logger.js
DELETED
|
@@ -1,731 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 日志系统 - Bun 环境自定义实现(替换 pino / pino-roll)
|
|
3
|
-
*/
|
|
4
|
-
import { createWriteStream, existsSync, mkdirSync } from "node:fs";
|
|
5
|
-
import { stat } from "node:fs/promises";
|
|
6
|
-
import { hostname as osHostname } from "node:os";
|
|
7
|
-
import { isAbsolute as nodePathIsAbsolute, join as nodePathJoin, resolve as nodePathResolve } from "node:path";
|
|
8
|
-
import { formatYmdHms } from "../utils/formatYmdHms";
|
|
9
|
-
import { buildSensitiveKeyMatcher, sanitizeLogObject } from "../utils/loggerUtils";
|
|
10
|
-
import { isPlainObject, normalizePositiveInt } from "../utils/util";
|
|
11
|
-
import { getCtx } from "./asyncContext";
|
|
12
|
-
// 注意:Logger 可能在运行时/测试中被 process.chdir() 影响。
|
|
13
|
-
// 为避免相对路径的 logs 目录随着 cwd 变化,使用模块加载时的初始 cwd 作为锚点。
|
|
14
|
-
const INITIAL_CWD = process.cwd();
|
|
15
|
-
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"];
|
|
16
|
-
let sanitizeOptions = {
|
|
17
|
-
maxStringLen: 200,
|
|
18
|
-
maxArrayItems: 500,
|
|
19
|
-
sanitizeDepth: 5,
|
|
20
|
-
sanitizeNodes: 5000,
|
|
21
|
-
sanitizeObjectKeys: 500,
|
|
22
|
-
sensitiveKeyMatcher: buildSensitiveKeyMatcher({ builtinPatterns: BUILTIN_SENSITIVE_KEYS, userPatterns: [] })
|
|
23
|
-
};
|
|
24
|
-
function buildSanitizeOptionsForWriteOptions(writeOptions) {
|
|
25
|
-
if (!writeOptions || writeOptions.truncate !== false)
|
|
26
|
-
return sanitizeOptions;
|
|
27
|
-
// 仅关闭“截断”,仍保留敏感字段掩码与结构化清洗(避免泄露敏感信息)。
|
|
28
|
-
return {
|
|
29
|
-
maxStringLen: 200000,
|
|
30
|
-
maxArrayItems: 5000,
|
|
31
|
-
sanitizeDepth: sanitizeOptions.sanitizeDepth,
|
|
32
|
-
sanitizeNodes: sanitizeOptions.sanitizeNodes,
|
|
33
|
-
sanitizeObjectKeys: sanitizeOptions.sanitizeObjectKeys,
|
|
34
|
-
sensitiveKeyMatcher: sanitizeOptions.sensitiveKeyMatcher
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
const HOSTNAME = (() => {
|
|
38
|
-
try {
|
|
39
|
-
return osHostname();
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return "unknown";
|
|
43
|
-
}
|
|
44
|
-
})();
|
|
45
|
-
let mockInstance = null;
|
|
46
|
-
let appFileSink = null;
|
|
47
|
-
let errorFileSink = null;
|
|
48
|
-
let appConsoleSink = null;
|
|
49
|
-
let config = {
|
|
50
|
-
debug: 0,
|
|
51
|
-
dir: "./logs",
|
|
52
|
-
console: 1,
|
|
53
|
-
maxSize: 20
|
|
54
|
-
};
|
|
55
|
-
function safeWriteStderr(msg) {
|
|
56
|
-
try {
|
|
57
|
-
process.stderr.write(`${msg}\n`);
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// ignore
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function shiftBatchFromPending(pending, maxBatchBytes) {
|
|
64
|
-
const parts = [];
|
|
65
|
-
let bytes = 0;
|
|
66
|
-
while (pending.length > 0) {
|
|
67
|
-
const next = pending[0];
|
|
68
|
-
const nextBytes = Buffer.byteLength(next);
|
|
69
|
-
if (parts.length > 0 && bytes + nextBytes > maxBatchBytes) {
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
parts.push(next);
|
|
73
|
-
bytes = bytes + nextBytes;
|
|
74
|
-
pending.shift();
|
|
75
|
-
}
|
|
76
|
-
return { chunk: parts.join(""), bytes: bytes };
|
|
77
|
-
}
|
|
78
|
-
class BufferedSink {
|
|
79
|
-
pending;
|
|
80
|
-
pendingBytes;
|
|
81
|
-
scheduledTimer;
|
|
82
|
-
flushing;
|
|
83
|
-
maxBufferBytes;
|
|
84
|
-
flushDelayMs;
|
|
85
|
-
maxBatchBytes;
|
|
86
|
-
writeChunk;
|
|
87
|
-
onShutdown;
|
|
88
|
-
constructor(options) {
|
|
89
|
-
this.pending = [];
|
|
90
|
-
this.pendingBytes = 0;
|
|
91
|
-
this.scheduledTimer = null;
|
|
92
|
-
this.flushing = false;
|
|
93
|
-
this.maxBufferBytes = options.maxBufferBytes;
|
|
94
|
-
this.flushDelayMs = options.flushDelayMs;
|
|
95
|
-
this.maxBatchBytes = options.maxBatchBytes;
|
|
96
|
-
this.writeChunk = options.writeChunk;
|
|
97
|
-
this.onShutdown = options.onShutdown ? options.onShutdown : null;
|
|
98
|
-
}
|
|
99
|
-
scheduleFlush() {
|
|
100
|
-
if (this.scheduledTimer)
|
|
101
|
-
return;
|
|
102
|
-
this.scheduledTimer = setTimeout(() => {
|
|
103
|
-
// timer 触发时先清空句柄,避免 flush 内再次 schedule 时被认为“已安排”。
|
|
104
|
-
this.scheduledTimer = null;
|
|
105
|
-
void this.flush();
|
|
106
|
-
}, this.flushDelayMs);
|
|
107
|
-
}
|
|
108
|
-
cancelScheduledFlush() {
|
|
109
|
-
if (!this.scheduledTimer)
|
|
110
|
-
return;
|
|
111
|
-
clearTimeout(this.scheduledTimer);
|
|
112
|
-
this.scheduledTimer = null;
|
|
113
|
-
}
|
|
114
|
-
enqueue(line) {
|
|
115
|
-
const bytes = Buffer.byteLength(line);
|
|
116
|
-
if (this.pendingBytes + bytes > this.maxBufferBytes) {
|
|
117
|
-
// buffer 满:统一丢弃新日志(不区分 level)
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
this.pending.push(line);
|
|
121
|
-
this.pendingBytes = this.pendingBytes + bytes;
|
|
122
|
-
this.scheduleFlush();
|
|
123
|
-
}
|
|
124
|
-
async flushNow() {
|
|
125
|
-
// 若已安排了定时 flush,flushNow 会覆盖它,避免出现多个 timer 并存。
|
|
126
|
-
this.cancelScheduledFlush();
|
|
127
|
-
await this.flush();
|
|
128
|
-
}
|
|
129
|
-
async shutdown() {
|
|
130
|
-
this.cancelScheduledFlush();
|
|
131
|
-
await this.flush();
|
|
132
|
-
if (this.onShutdown) {
|
|
133
|
-
await this.onShutdown();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
async flush() {
|
|
137
|
-
if (this.flushing)
|
|
138
|
-
return;
|
|
139
|
-
this.flushing = true;
|
|
140
|
-
try {
|
|
141
|
-
while (this.pending.length > 0) {
|
|
142
|
-
const batch = shiftBatchFromPending(this.pending, this.maxBatchBytes);
|
|
143
|
-
const chunk = batch.chunk;
|
|
144
|
-
const chunkBytes = Buffer.byteLength(chunk);
|
|
145
|
-
this.pendingBytes = this.pendingBytes - chunkBytes;
|
|
146
|
-
const ok = await this.writeChunk(chunk, chunkBytes);
|
|
147
|
-
if (!ok) {
|
|
148
|
-
// writer 已禁用/失败:清空剩余 pending
|
|
149
|
-
this.pending = [];
|
|
150
|
-
this.pendingBytes = 0;
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
finally {
|
|
156
|
-
this.flushing = false;
|
|
157
|
-
if (this.pending.length > 0) {
|
|
158
|
-
this.scheduleFlush();
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
function createStreamSink(kind) {
|
|
164
|
-
const getStream = () => {
|
|
165
|
-
return kind === "stderr" ? process.stderr : process.stdout;
|
|
166
|
-
};
|
|
167
|
-
const buffer = new BufferedSink({
|
|
168
|
-
maxBufferBytes: 10 * 1024 * 1024,
|
|
169
|
-
flushDelayMs: 10,
|
|
170
|
-
maxBatchBytes: 64 * 1024,
|
|
171
|
-
writeChunk: async (chunk) => {
|
|
172
|
-
try {
|
|
173
|
-
const stream = getStream();
|
|
174
|
-
const ok = stream.write(chunk);
|
|
175
|
-
if (!ok) {
|
|
176
|
-
await new Promise((resolve) => {
|
|
177
|
-
stream.once("drain", () => resolve());
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
catch (error) {
|
|
183
|
-
safeWriteStderr(`[Logger] stream sink error: ${error?.message || error}`);
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
return {
|
|
189
|
-
enqueue(line) {
|
|
190
|
-
buffer.enqueue(line);
|
|
191
|
-
},
|
|
192
|
-
async flushNow() {
|
|
193
|
-
await buffer.flushNow();
|
|
194
|
-
},
|
|
195
|
-
async shutdown() {
|
|
196
|
-
await buffer.shutdown();
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
class LogFileSink {
|
|
201
|
-
prefix;
|
|
202
|
-
maxFileBytes;
|
|
203
|
-
buffer;
|
|
204
|
-
stream;
|
|
205
|
-
streamDate;
|
|
206
|
-
streamIndex;
|
|
207
|
-
streamSizeBytes;
|
|
208
|
-
disabled;
|
|
209
|
-
constructor(options) {
|
|
210
|
-
this.prefix = options.prefix;
|
|
211
|
-
this.maxFileBytes = options.maxFileBytes;
|
|
212
|
-
this.stream = null;
|
|
213
|
-
this.streamDate = "";
|
|
214
|
-
this.streamIndex = 0;
|
|
215
|
-
this.streamSizeBytes = 0;
|
|
216
|
-
this.disabled = false;
|
|
217
|
-
this.buffer = new BufferedSink({
|
|
218
|
-
maxBufferBytes: 10 * 1024 * 1024,
|
|
219
|
-
flushDelayMs: 10,
|
|
220
|
-
maxBatchBytes: 64 * 1024,
|
|
221
|
-
writeChunk: async (chunk, chunkBytes) => {
|
|
222
|
-
if (this.disabled)
|
|
223
|
-
return false;
|
|
224
|
-
try {
|
|
225
|
-
await this.ensureStreamReady(chunkBytes);
|
|
226
|
-
if (!this.stream) {
|
|
227
|
-
// 文件 sink 已被禁用或打开失败
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
const ok = this.stream.write(chunk);
|
|
231
|
-
if (!ok) {
|
|
232
|
-
await new Promise((resolve) => {
|
|
233
|
-
const stream = this.stream;
|
|
234
|
-
if (!stream) {
|
|
235
|
-
resolve();
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
stream.once("drain", () => resolve());
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
this.streamSizeBytes = this.streamSizeBytes + chunkBytes;
|
|
242
|
-
return true;
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
safeWriteStderr(`[Logger] file sink flush error (${this.prefix}): ${error?.message || error}`);
|
|
246
|
-
this.disabled = true;
|
|
247
|
-
await this.closeStream();
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
onShutdown: async () => {
|
|
252
|
-
await this.closeStream();
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
enqueue(line) {
|
|
257
|
-
if (this.disabled)
|
|
258
|
-
return;
|
|
259
|
-
this.buffer.enqueue(line);
|
|
260
|
-
}
|
|
261
|
-
async flushNow() {
|
|
262
|
-
await this.buffer.flushNow();
|
|
263
|
-
}
|
|
264
|
-
async shutdown() {
|
|
265
|
-
await this.buffer.shutdown();
|
|
266
|
-
}
|
|
267
|
-
async closeStream() {
|
|
268
|
-
if (!this.stream)
|
|
269
|
-
return;
|
|
270
|
-
const st = this.stream;
|
|
271
|
-
this.stream = null;
|
|
272
|
-
await new Promise((resolve) => {
|
|
273
|
-
try {
|
|
274
|
-
st.end(() => resolve());
|
|
275
|
-
}
|
|
276
|
-
catch {
|
|
277
|
-
resolve();
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
openStream(filePath) {
|
|
282
|
-
try {
|
|
283
|
-
this.stream = createWriteStream(filePath, { flags: "a" });
|
|
284
|
-
this.stream.on("error", (error) => {
|
|
285
|
-
safeWriteStderr(`[Logger] file sink error (${this.prefix}): ${error?.message || error}`);
|
|
286
|
-
this.disabled = true;
|
|
287
|
-
void this.closeStream();
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
catch (error) {
|
|
291
|
-
safeWriteStderr(`[Logger] createWriteStream failed (${this.prefix}): ${error?.message || error}`);
|
|
292
|
-
this.disabled = true;
|
|
293
|
-
this.stream = null;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
getFilePath(date, index) {
|
|
297
|
-
const suffix = index > 0 ? `.${index}` : "";
|
|
298
|
-
const filename = `${this.prefix}.${date}${suffix}.log`;
|
|
299
|
-
return nodePathJoin(resolveLogDir(), filename);
|
|
300
|
-
}
|
|
301
|
-
async ensureStreamReady(nextChunkBytes) {
|
|
302
|
-
const date = formatYmdHms(new Date(), "date");
|
|
303
|
-
// 日期变化:切新文件
|
|
304
|
-
if (this.stream && this.streamDate && date !== this.streamDate) {
|
|
305
|
-
await this.closeStream();
|
|
306
|
-
this.streamDate = "";
|
|
307
|
-
this.streamIndex = 0;
|
|
308
|
-
this.streamSizeBytes = 0;
|
|
309
|
-
}
|
|
310
|
-
// 首次打开
|
|
311
|
-
if (!this.stream) {
|
|
312
|
-
const filePath = this.getFilePath(date, 0);
|
|
313
|
-
let size = 0;
|
|
314
|
-
try {
|
|
315
|
-
const st = await stat(filePath);
|
|
316
|
-
size = typeof st.size === "number" ? st.size : 0;
|
|
317
|
-
}
|
|
318
|
-
catch {
|
|
319
|
-
size = 0;
|
|
320
|
-
}
|
|
321
|
-
this.streamDate = date;
|
|
322
|
-
this.streamIndex = 0;
|
|
323
|
-
this.streamSizeBytes = size;
|
|
324
|
-
this.openStream(filePath);
|
|
325
|
-
if (!this.stream)
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
// 大小滚动
|
|
329
|
-
if (this.stream && this.maxFileBytes > 0 && this.streamSizeBytes + nextChunkBytes > this.maxFileBytes) {
|
|
330
|
-
await this.closeStream();
|
|
331
|
-
this.streamIndex = this.streamIndex + 1;
|
|
332
|
-
const filePath = this.getFilePath(date, this.streamIndex);
|
|
333
|
-
this.streamDate = date;
|
|
334
|
-
this.streamSizeBytes = 0;
|
|
335
|
-
this.openStream(filePath);
|
|
336
|
-
if (!this.stream)
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
export async function flush() {
|
|
342
|
-
// 测试场景:mock logger 不需要 flush
|
|
343
|
-
if (mockInstance)
|
|
344
|
-
return;
|
|
345
|
-
const sinks = [];
|
|
346
|
-
if (appFileSink)
|
|
347
|
-
sinks.push({ flush: () => (appFileSink ? appFileSink.flushNow() : Promise.resolve()) });
|
|
348
|
-
if (errorFileSink)
|
|
349
|
-
sinks.push({ flush: () => (errorFileSink ? errorFileSink.flushNow() : Promise.resolve()) });
|
|
350
|
-
if (appConsoleSink)
|
|
351
|
-
sinks.push({ flush: () => (appConsoleSink ? appConsoleSink.flushNow() : Promise.resolve()) });
|
|
352
|
-
for (const item of sinks) {
|
|
353
|
-
try {
|
|
354
|
-
await item.flush();
|
|
355
|
-
}
|
|
356
|
-
catch {
|
|
357
|
-
// ignore
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
export async function shutdown() {
|
|
362
|
-
// 测试场景:mock logger 不需要 shutdown
|
|
363
|
-
if (mockInstance)
|
|
364
|
-
return;
|
|
365
|
-
// 重要:shutdown 可能与后续 Logger.getLogger() 并发。
|
|
366
|
-
// 因此这里捕获“当前的旧 sink/instance 快照”,只关闭这些快照,避免把新创建的 sink 一并清掉。
|
|
367
|
-
const currentAppFileSink = appFileSink;
|
|
368
|
-
const currentErrorFileSink = errorFileSink;
|
|
369
|
-
const currentAppConsoleSink = appConsoleSink;
|
|
370
|
-
const sinks = [];
|
|
371
|
-
if (currentAppFileSink)
|
|
372
|
-
sinks.push({ shutdown: () => currentAppFileSink.shutdown() });
|
|
373
|
-
if (currentErrorFileSink)
|
|
374
|
-
sinks.push({ shutdown: () => currentErrorFileSink.shutdown() });
|
|
375
|
-
if (currentAppConsoleSink)
|
|
376
|
-
sinks.push({ shutdown: () => currentAppConsoleSink.shutdown() });
|
|
377
|
-
for (const item of sinks) {
|
|
378
|
-
try {
|
|
379
|
-
await item.shutdown();
|
|
380
|
-
}
|
|
381
|
-
catch {
|
|
382
|
-
// ignore
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
if (appFileSink === currentAppFileSink) {
|
|
386
|
-
appFileSink = null;
|
|
387
|
-
}
|
|
388
|
-
if (errorFileSink === currentErrorFileSink) {
|
|
389
|
-
errorFileSink = null;
|
|
390
|
-
}
|
|
391
|
-
if (appConsoleSink === currentAppConsoleSink) {
|
|
392
|
-
appConsoleSink = null;
|
|
393
|
-
}
|
|
394
|
-
// shutdown 后允许下一次重新初始化时再次校验/创建目录(测试会清理目录,避免 ENOENT)
|
|
395
|
-
// 无需缓存状态:确保目录存在是幂等的。
|
|
396
|
-
}
|
|
397
|
-
function resolveLogDir() {
|
|
398
|
-
const rawDir = config.dir || "./logs";
|
|
399
|
-
if (nodePathIsAbsolute(rawDir)) {
|
|
400
|
-
return rawDir;
|
|
401
|
-
}
|
|
402
|
-
return nodePathResolve(INITIAL_CWD, rawDir);
|
|
403
|
-
}
|
|
404
|
-
function ensureLogDirExists() {
|
|
405
|
-
const dir = resolveLogDir();
|
|
406
|
-
try {
|
|
407
|
-
if (!existsSync(dir)) {
|
|
408
|
-
mkdirSync(dir, { recursive: true });
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
catch (error) {
|
|
412
|
-
// 不能在 Logger 初始化前调用 Logger 本身,直接抛错即可
|
|
413
|
-
throw new Error(`创建 logs 目录失败: ${dir}. ${error?.message || error}`);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
// 方案B:删除“启动时清理旧日志”功能(减少 I/O 与复杂度)。
|
|
417
|
-
/**
|
|
418
|
-
* 配置日志
|
|
419
|
-
*/
|
|
420
|
-
export function configure(cfg) {
|
|
421
|
-
// 旧实例可能仍持有文件句柄;这里异步关闭(不阻塞主流程)
|
|
422
|
-
void shutdown();
|
|
423
|
-
// 方案B:每次 configure 都从默认配置重新构建(避免继承上一次配置造成测试/运行时污染)
|
|
424
|
-
config = Object.assign({
|
|
425
|
-
debug: 0,
|
|
426
|
-
dir: "./logs",
|
|
427
|
-
console: 1,
|
|
428
|
-
maxSize: 20
|
|
429
|
-
}, cfg);
|
|
430
|
-
// maxSize:仅按 MB 计算,且强制范围 10..100
|
|
431
|
-
{
|
|
432
|
-
const raw = config.maxSize;
|
|
433
|
-
let mb = typeof raw === "number" && Number.isFinite(raw) ? raw : 20;
|
|
434
|
-
if (mb < 10)
|
|
435
|
-
mb = 10;
|
|
436
|
-
if (mb > 100)
|
|
437
|
-
mb = 100;
|
|
438
|
-
config.maxSize = mb;
|
|
439
|
-
}
|
|
440
|
-
appFileSink = null;
|
|
441
|
-
errorFileSink = null;
|
|
442
|
-
appConsoleSink = null;
|
|
443
|
-
// 运行时清洗/截断上限(可配置)
|
|
444
|
-
sanitizeOptions = {
|
|
445
|
-
maxStringLen: normalizePositiveInt(config.maxStringLen, 200, 20, 200000),
|
|
446
|
-
maxArrayItems: normalizePositiveInt(config.maxArrayItems, 500, 10, 5000),
|
|
447
|
-
sanitizeDepth: normalizePositiveInt(config.sanitizeDepth, 5, 1, 10),
|
|
448
|
-
sanitizeNodes: normalizePositiveInt(config.sanitizeNodes, 5000, 50, 20000),
|
|
449
|
-
sanitizeObjectKeys: normalizePositiveInt(config.sanitizeObjectKeys, 500, 10, 5000),
|
|
450
|
-
sensitiveKeyMatcher: buildSensitiveKeyMatcher({ builtinPatterns: BUILTIN_SENSITIVE_KEYS, userPatterns: config.excludeFields })
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* 设置 Mock Logger(用于测试)
|
|
455
|
-
* @param mock - Mock Logger 实例(形如 {info/warn/error/debug}),传 null 清除 mock
|
|
456
|
-
*/
|
|
457
|
-
export function setMockLogger(mock) {
|
|
458
|
-
mockInstance = mock;
|
|
459
|
-
}
|
|
460
|
-
function getSink(kind) {
|
|
461
|
-
// 优先返回 mock 实例(用于测试)
|
|
462
|
-
if (mockInstance)
|
|
463
|
-
return mockInstance;
|
|
464
|
-
return {
|
|
465
|
-
info(record) {
|
|
466
|
-
writeJsonl(kind, "info", record, undefined);
|
|
467
|
-
},
|
|
468
|
-
warn(record) {
|
|
469
|
-
writeJsonl(kind, "warn", record, undefined);
|
|
470
|
-
},
|
|
471
|
-
error(record) {
|
|
472
|
-
writeJsonl(kind, "error", record, undefined);
|
|
473
|
-
},
|
|
474
|
-
debug(record) {
|
|
475
|
-
writeJsonl(kind, "debug", record, undefined);
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* 获取 Logger 实例(延迟初始化)
|
|
481
|
-
*/
|
|
482
|
-
export function getLogger() {
|
|
483
|
-
return getSink("app");
|
|
484
|
-
}
|
|
485
|
-
function buildBaseFields(level, timeMs) {
|
|
486
|
-
const base = {
|
|
487
|
-
level: level,
|
|
488
|
-
time: timeMs,
|
|
489
|
-
timeFormat: formatYmdHms(new Date(timeMs)),
|
|
490
|
-
pid: process.pid,
|
|
491
|
-
hostname: HOSTNAME
|
|
492
|
-
};
|
|
493
|
-
return base;
|
|
494
|
-
}
|
|
495
|
-
function buildLogLineWithBase(base, record) {
|
|
496
|
-
// 目标:让 level/time/timeFormat/pid/hostname 在 JSON 行首出现(更易读),同时保持 JSONL 可解析。
|
|
497
|
-
// 约束:record 不允许覆盖基础字段。
|
|
498
|
-
// 实现:先写入基础字段,再按 record 的 key 顺序追加其它字段。
|
|
499
|
-
if (record && isPlainObject(record)) {
|
|
500
|
-
const out = {};
|
|
501
|
-
out["level"] = base["level"];
|
|
502
|
-
out["time"] = base["time"];
|
|
503
|
-
out["timeFormat"] = base["timeFormat"];
|
|
504
|
-
out["pid"] = base["pid"];
|
|
505
|
-
out["hostname"] = base["hostname"];
|
|
506
|
-
for (const key of Object.keys(record)) {
|
|
507
|
-
if (key === "level")
|
|
508
|
-
continue;
|
|
509
|
-
if (key === "time")
|
|
510
|
-
continue;
|
|
511
|
-
if (key === "timeFormat")
|
|
512
|
-
continue;
|
|
513
|
-
if (key === "pid")
|
|
514
|
-
continue;
|
|
515
|
-
if (key === "hostname")
|
|
516
|
-
continue;
|
|
517
|
-
out[key] = record[key];
|
|
518
|
-
}
|
|
519
|
-
// msg 允许保留 record.msg(若存在),否则补齐空字符串。
|
|
520
|
-
if (record.msg !== undefined) {
|
|
521
|
-
out["msg"] = record.msg;
|
|
522
|
-
}
|
|
523
|
-
else if (out["msg"] === undefined) {
|
|
524
|
-
out["msg"] = "";
|
|
525
|
-
}
|
|
526
|
-
return `${safeJsonStringify(out)}\n`;
|
|
527
|
-
}
|
|
528
|
-
if (base["msg"] === undefined) {
|
|
529
|
-
base["msg"] = "";
|
|
530
|
-
}
|
|
531
|
-
return `${safeJsonStringify(base)}\n`;
|
|
532
|
-
}
|
|
533
|
-
function safeJsonStringify(obj) {
|
|
534
|
-
try {
|
|
535
|
-
return JSON.stringify(obj);
|
|
536
|
-
}
|
|
537
|
-
catch {
|
|
538
|
-
try {
|
|
539
|
-
return JSON.stringify({ level: obj["level"], time: obj["time"], pid: obj["pid"], hostname: obj["hostname"], msg: "[Unserializable log record]" });
|
|
540
|
-
}
|
|
541
|
-
catch {
|
|
542
|
-
return '{"msg":"[Unserializable log record]"}';
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
function ensureSinksReady(kind) {
|
|
547
|
-
ensureLogDirExists();
|
|
548
|
-
const maxSizeMb = typeof config.maxSize === "number" ? config.maxSize : 20;
|
|
549
|
-
const maxFileBytes = Math.floor(maxSizeMb * 1024 * 1024);
|
|
550
|
-
if (kind === "app") {
|
|
551
|
-
if (!appFileSink) {
|
|
552
|
-
appFileSink = new LogFileSink({ prefix: "app", maxFileBytes: maxFileBytes });
|
|
553
|
-
}
|
|
554
|
-
if (config.console === 1 && !appConsoleSink) {
|
|
555
|
-
appConsoleSink = createStreamSink("stdout");
|
|
556
|
-
}
|
|
557
|
-
return { fileSink: appFileSink, consoleSink: config.console === 1 ? appConsoleSink : null };
|
|
558
|
-
}
|
|
559
|
-
if (!errorFileSink) {
|
|
560
|
-
errorFileSink = new LogFileSink({ prefix: "error", maxFileBytes: maxFileBytes });
|
|
561
|
-
}
|
|
562
|
-
return { fileSink: errorFileSink, consoleSink: null };
|
|
563
|
-
}
|
|
564
|
-
function writeJsonl(kind, level, record, options) {
|
|
565
|
-
if (level === "debug" && config.debug !== 1)
|
|
566
|
-
return;
|
|
567
|
-
const sinks = ensureSinksReady(kind);
|
|
568
|
-
const time = Date.now();
|
|
569
|
-
const base = buildBaseFields(level, time);
|
|
570
|
-
const effectiveSanitizeOptions = buildSanitizeOptionsForWriteOptions(options);
|
|
571
|
-
const sanitizedRecord = sanitizeLogObject(record, effectiveSanitizeOptions);
|
|
572
|
-
const fileLine = buildLogLineWithBase(base, sanitizedRecord);
|
|
573
|
-
sinks.fileSink.enqueue(fileLine);
|
|
574
|
-
if (sinks.consoleSink) {
|
|
575
|
-
if (!options || options.console !== false) {
|
|
576
|
-
sinks.consoleSink.enqueue(fileLine);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
// 对象清洗/脱敏/截断逻辑已下沉到 utils/loggerUtils.ts(减少 logger.ts 复杂度)。
|
|
581
|
-
function metaToObject() {
|
|
582
|
-
const meta = getCtx();
|
|
583
|
-
if (!meta)
|
|
584
|
-
return null;
|
|
585
|
-
const durationSinceNowMs = Date.now() - meta.now;
|
|
586
|
-
const obj = {
|
|
587
|
-
requestId: meta.requestId,
|
|
588
|
-
method: meta.method,
|
|
589
|
-
route: meta.route,
|
|
590
|
-
ip: meta.ip,
|
|
591
|
-
now: meta.now,
|
|
592
|
-
durationSinceNowMs: durationSinceNowMs
|
|
593
|
-
};
|
|
594
|
-
// userId / roleCode 默认写入
|
|
595
|
-
obj["userId"] = meta.userId;
|
|
596
|
-
obj["roleCode"] = meta.roleCode;
|
|
597
|
-
obj["nickname"] = meta.nickname;
|
|
598
|
-
obj["roleType"] = meta.roleType;
|
|
599
|
-
return obj;
|
|
600
|
-
}
|
|
601
|
-
function mergeMetaIntoObject(input, meta) {
|
|
602
|
-
const merged = {};
|
|
603
|
-
for (const key of Object.keys(input)) {
|
|
604
|
-
merged[key] = input[key];
|
|
605
|
-
}
|
|
606
|
-
// 只补齐、不覆盖:允许把 undefined / null / 空字符串写入(由日志底层序列化决定是否展示)
|
|
607
|
-
const keys = ["requestId", "method", "route", "ip", "now", "durationSinceNowMs", "userId", "roleCode", "nickname", "roleType"];
|
|
608
|
-
for (const key of keys) {
|
|
609
|
-
if (merged[key] === undefined)
|
|
610
|
-
merged[key] = meta[key];
|
|
611
|
-
}
|
|
612
|
-
return merged;
|
|
613
|
-
}
|
|
614
|
-
// 日志仅接受 1 个入参(任意类型)。
|
|
615
|
-
// - plain object({})直接作为 record
|
|
616
|
-
// - 其他任何类型:包装成对象再写入(避免 sink 层依赖入参类型)
|
|
617
|
-
function toRecord(input) {
|
|
618
|
-
if (isPlainObject(input)) {
|
|
619
|
-
return input;
|
|
620
|
-
}
|
|
621
|
-
if (input instanceof Error) {
|
|
622
|
-
return { err: input, msg: input.message || input.name || "Error" };
|
|
623
|
-
}
|
|
624
|
-
if (input === undefined) {
|
|
625
|
-
return { msg: "" };
|
|
626
|
-
}
|
|
627
|
-
if (input === null) {
|
|
628
|
-
return { msg: "null" };
|
|
629
|
-
}
|
|
630
|
-
// 非 plain object(数组/Date/Map/...)也走 value 包装,由 sanitize 负责做安全预览/截断。
|
|
631
|
-
if (typeof input === "object") {
|
|
632
|
-
return { value: input };
|
|
633
|
-
}
|
|
634
|
-
try {
|
|
635
|
-
return { msg: String(input) };
|
|
636
|
-
}
|
|
637
|
-
catch {
|
|
638
|
-
return { msg: "[Unserializable]" };
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
function withRequestMetaRecord(record) {
|
|
642
|
-
const meta = metaToObject();
|
|
643
|
-
if (!meta)
|
|
644
|
-
return record;
|
|
645
|
-
return mergeMetaIntoObject(record, meta);
|
|
646
|
-
}
|
|
647
|
-
class LoggerFacade {
|
|
648
|
-
write(level, input, options) {
|
|
649
|
-
// debug!=1 则完全不记录 debug 日志(包括文件与控制台)
|
|
650
|
-
if (level === "debug" && config.debug !== 1)
|
|
651
|
-
return;
|
|
652
|
-
const record0 = withRequestMetaRecord(toRecord(input));
|
|
653
|
-
// 测试场景:mock logger 走同步写入,并在 facade 层进行清洗/脱敏/截断控制
|
|
654
|
-
if (mockInstance) {
|
|
655
|
-
const effective = buildSanitizeOptionsForWriteOptions(options);
|
|
656
|
-
const sanitized = sanitizeLogObject(record0, effective);
|
|
657
|
-
if (level === "info") {
|
|
658
|
-
mockInstance.info(sanitized);
|
|
659
|
-
}
|
|
660
|
-
else if (level === "warn") {
|
|
661
|
-
mockInstance.warn(sanitized);
|
|
662
|
-
}
|
|
663
|
-
else if (level === "error") {
|
|
664
|
-
mockInstance.error(sanitized);
|
|
665
|
-
}
|
|
666
|
-
else {
|
|
667
|
-
mockInstance.debug(sanitized);
|
|
668
|
-
}
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
writeJsonl("app", level, record0, options);
|
|
672
|
-
if (level === "error") {
|
|
673
|
-
// error 专属文件:始终镜像一份
|
|
674
|
-
writeJsonl("error", "error", record0, options);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
info(input, options) {
|
|
678
|
-
this.write("info", input, options);
|
|
679
|
-
}
|
|
680
|
-
warn(input, options) {
|
|
681
|
-
this.write("warn", input, options);
|
|
682
|
-
}
|
|
683
|
-
error(input, options) {
|
|
684
|
-
this.write("error", input, options);
|
|
685
|
-
}
|
|
686
|
-
debug(input, options) {
|
|
687
|
-
this.write("debug", input, options);
|
|
688
|
-
}
|
|
689
|
-
/**
|
|
690
|
-
* 打印多行纯文本到控制台(stdout/stderr)。
|
|
691
|
-
*
|
|
692
|
-
* 说明:
|
|
693
|
-
* - 该方法不写入 JSONL 文件、不做清洗/脱敏/截断;仅用于“人类可读”的多行输出。
|
|
694
|
-
* - 若需要落盘/采集:请先调用 Logger.info/warn/error/debug 写入 JSONL(可选 console:false),再调用本方法输出多行。
|
|
695
|
-
*/
|
|
696
|
-
printPlainLines(input, options) {
|
|
697
|
-
const kind = options && options.stream === "stderr" ? "stderr" : "stdout";
|
|
698
|
-
const stream = kind === "stderr" ? process.stderr : process.stdout;
|
|
699
|
-
try {
|
|
700
|
-
if (Array.isArray(input)) {
|
|
701
|
-
for (const line of input) {
|
|
702
|
-
stream.write(`${String(line)}\n`);
|
|
703
|
-
}
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
const parts = String(input).split("\n");
|
|
707
|
-
for (let i = 0; i < parts.length; i = i + 1) {
|
|
708
|
-
stream.write(`${parts[i]}\n`);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
catch {
|
|
712
|
-
// ignore
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
async flush() {
|
|
716
|
-
await flush();
|
|
717
|
-
}
|
|
718
|
-
configure(cfg) {
|
|
719
|
-
configure(cfg);
|
|
720
|
-
}
|
|
721
|
-
setMock(mock) {
|
|
722
|
-
setMockLogger(mock);
|
|
723
|
-
}
|
|
724
|
-
async shutdown() {
|
|
725
|
-
await shutdown();
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* 日志实例(延迟初始化)
|
|
730
|
-
*/
|
|
731
|
-
export const Logger = new LoggerFacade();
|