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.
Files changed (178) hide show
  1. package/README.md +0 -129
  2. package/befly.js +12769 -0
  3. package/befly.min.js +47 -0
  4. package/package.json +18 -29
  5. package/dist/befly.config.d.ts +0 -7
  6. package/dist/befly.config.js +0 -128
  7. package/dist/befly.js +0 -17348
  8. package/dist/befly.min.js +0 -23
  9. package/dist/checks/checkApi.d.ts +0 -1
  10. package/dist/checks/checkApi.js +0 -139
  11. package/dist/checks/checkConfig.d.ts +0 -9
  12. package/dist/checks/checkConfig.js +0 -255
  13. package/dist/checks/checkHook.d.ts +0 -1
  14. package/dist/checks/checkHook.js +0 -132
  15. package/dist/checks/checkMenu.d.ts +0 -3
  16. package/dist/checks/checkMenu.js +0 -106
  17. package/dist/checks/checkPlugin.d.ts +0 -1
  18. package/dist/checks/checkPlugin.js +0 -132
  19. package/dist/checks/checkTable.d.ts +0 -7
  20. package/dist/checks/checkTable.js +0 -431
  21. package/dist/configs/presetRegexp.d.ts +0 -145
  22. package/dist/configs/presetRegexp.js +0 -218
  23. package/dist/hooks/auth.d.ts +0 -3
  24. package/dist/hooks/auth.js +0 -24
  25. package/dist/hooks/cors.d.ts +0 -7
  26. package/dist/hooks/cors.js +0 -36
  27. package/dist/hooks/parser.d.ts +0 -10
  28. package/dist/hooks/parser.js +0 -76
  29. package/dist/hooks/permission.d.ts +0 -11
  30. package/dist/hooks/permission.js +0 -78
  31. package/dist/hooks/validator.d.ts +0 -7
  32. package/dist/hooks/validator.js +0 -52
  33. package/dist/index.d.ts +0 -28
  34. package/dist/index.js +0 -316
  35. package/dist/lib/asyncContext.d.ts +0 -21
  36. package/dist/lib/asyncContext.js +0 -27
  37. package/dist/lib/cacheHelper.d.ts +0 -128
  38. package/dist/lib/cacheHelper.js +0 -477
  39. package/dist/lib/cacheKeys.d.ts +0 -27
  40. package/dist/lib/cacheKeys.js +0 -37
  41. package/dist/lib/cipher.d.ts +0 -153
  42. package/dist/lib/cipher.js +0 -237
  43. package/dist/lib/connect.d.ts +0 -95
  44. package/dist/lib/connect.js +0 -313
  45. package/dist/lib/dbHelper.d.ts +0 -229
  46. package/dist/lib/dbHelper.js +0 -1099
  47. package/dist/lib/dbUtils.d.ts +0 -91
  48. package/dist/lib/dbUtils.js +0 -544
  49. package/dist/lib/jwt.d.ts +0 -13
  50. package/dist/lib/jwt.js +0 -77
  51. package/dist/lib/logger.d.ts +0 -46
  52. package/dist/lib/logger.js +0 -731
  53. package/dist/lib/redisHelper.d.ts +0 -193
  54. package/dist/lib/redisHelper.js +0 -598
  55. package/dist/lib/sqlBuilder.d.ts +0 -160
  56. package/dist/lib/sqlBuilder.js +0 -837
  57. package/dist/lib/sqlCheck.d.ts +0 -23
  58. package/dist/lib/sqlCheck.js +0 -119
  59. package/dist/lib/validator.d.ts +0 -45
  60. package/dist/lib/validator.js +0 -424
  61. package/dist/loader/loadApis.d.ts +0 -12
  62. package/dist/loader/loadApis.js +0 -71
  63. package/dist/loader/loadHooks.d.ts +0 -7
  64. package/dist/loader/loadHooks.js +0 -50
  65. package/dist/loader/loadPlugins.d.ts +0 -8
  66. package/dist/loader/loadPlugins.js +0 -69
  67. package/dist/paths.d.ts +0 -93
  68. package/dist/paths.js +0 -100
  69. package/dist/plugins/cache.d.ts +0 -10
  70. package/dist/plugins/cache.js +0 -24
  71. package/dist/plugins/cipher.d.ts +0 -7
  72. package/dist/plugins/cipher.js +0 -14
  73. package/dist/plugins/config.d.ts +0 -3
  74. package/dist/plugins/config.js +0 -9
  75. package/dist/plugins/db.d.ts +0 -10
  76. package/dist/plugins/db.js +0 -48
  77. package/dist/plugins/jwt.d.ts +0 -6
  78. package/dist/plugins/jwt.js +0 -13
  79. package/dist/plugins/logger.d.ts +0 -10
  80. package/dist/plugins/logger.js +0 -21
  81. package/dist/plugins/redis.d.ts +0 -10
  82. package/dist/plugins/redis.js +0 -40
  83. package/dist/plugins/tool.d.ts +0 -75
  84. package/dist/plugins/tool.js +0 -105
  85. package/dist/router/api.d.ts +0 -14
  86. package/dist/router/api.js +0 -109
  87. package/dist/router/static.d.ts +0 -9
  88. package/dist/router/static.js +0 -56
  89. package/dist/scripts/ensureDist.d.ts +0 -1
  90. package/dist/scripts/ensureDist.js +0 -296
  91. package/dist/sync/syncApi.d.ts +0 -3
  92. package/dist/sync/syncApi.js +0 -163
  93. package/dist/sync/syncCache.d.ts +0 -2
  94. package/dist/sync/syncCache.js +0 -14
  95. package/dist/sync/syncDev.d.ts +0 -6
  96. package/dist/sync/syncDev.js +0 -166
  97. package/dist/sync/syncMenu.d.ts +0 -14
  98. package/dist/sync/syncMenu.js +0 -308
  99. package/dist/sync/syncTable.d.ts +0 -126
  100. package/dist/sync/syncTable.js +0 -1129
  101. package/dist/types/api.d.ts +0 -177
  102. package/dist/types/api.js +0 -4
  103. package/dist/types/befly.d.ts +0 -231
  104. package/dist/types/befly.js +0 -4
  105. package/dist/types/cache.d.ts +0 -96
  106. package/dist/types/cache.js +0 -4
  107. package/dist/types/cipher.d.ts +0 -27
  108. package/dist/types/cipher.js +0 -7
  109. package/dist/types/common.d.ts +0 -127
  110. package/dist/types/common.js +0 -5
  111. package/dist/types/context.d.ts +0 -39
  112. package/dist/types/context.js +0 -4
  113. package/dist/types/coreError.d.ts +0 -31
  114. package/dist/types/coreError.js +0 -38
  115. package/dist/types/crypto.d.ts +0 -20
  116. package/dist/types/crypto.js +0 -4
  117. package/dist/types/database.d.ts +0 -182
  118. package/dist/types/database.js +0 -4
  119. package/dist/types/hook.d.ts +0 -30
  120. package/dist/types/hook.js +0 -19
  121. package/dist/types/jwt.d.ts +0 -76
  122. package/dist/types/jwt.js +0 -4
  123. package/dist/types/logger.d.ts +0 -95
  124. package/dist/types/logger.js +0 -6
  125. package/dist/types/plugin.d.ts +0 -27
  126. package/dist/types/plugin.js +0 -17
  127. package/dist/types/redis.d.ts +0 -80
  128. package/dist/types/redis.js +0 -4
  129. package/dist/types/roleApisCache.d.ts +0 -21
  130. package/dist/types/roleApisCache.js +0 -8
  131. package/dist/types/sync.d.ts +0 -93
  132. package/dist/types/sync.js +0 -4
  133. package/dist/types/table.d.ts +0 -34
  134. package/dist/types/table.js +0 -4
  135. package/dist/types/validate.d.ts +0 -113
  136. package/dist/types/validate.js +0 -4
  137. package/dist/utils/calcPerfTime.d.ts +0 -4
  138. package/dist/utils/calcPerfTime.js +0 -13
  139. package/dist/utils/cors.d.ts +0 -8
  140. package/dist/utils/cors.js +0 -17
  141. package/dist/utils/dbFieldRules.d.ts +0 -31
  142. package/dist/utils/dbFieldRules.js +0 -94
  143. package/dist/utils/fieldClear.d.ts +0 -11
  144. package/dist/utils/fieldClear.js +0 -57
  145. package/dist/utils/formatYmdHms.d.ts +0 -1
  146. package/dist/utils/formatYmdHms.js +0 -20
  147. package/dist/utils/getClientIp.d.ts +0 -6
  148. package/dist/utils/getClientIp.js +0 -39
  149. package/dist/utils/importDefault.d.ts +0 -1
  150. package/dist/utils/importDefault.js +0 -53
  151. package/dist/utils/isDirentDirectory.d.ts +0 -3
  152. package/dist/utils/isDirentDirectory.js +0 -18
  153. package/dist/utils/loadMenuConfigs.d.ts +0 -11
  154. package/dist/utils/loadMenuConfigs.js +0 -130
  155. package/dist/utils/loggerUtils.d.ts +0 -18
  156. package/dist/utils/loggerUtils.js +0 -171
  157. package/dist/utils/mergeAndConcat.d.ts +0 -7
  158. package/dist/utils/mergeAndConcat.js +0 -77
  159. package/dist/utils/normalizeFieldDefinition.d.ts +0 -18
  160. package/dist/utils/normalizeFieldDefinition.js +0 -27
  161. package/dist/utils/processInfo.d.ts +0 -26
  162. package/dist/utils/processInfo.js +0 -41
  163. package/dist/utils/response.d.ts +0 -20
  164. package/dist/utils/response.js +0 -96
  165. package/dist/utils/scanAddons.d.ts +0 -15
  166. package/dist/utils/scanAddons.js +0 -35
  167. package/dist/utils/scanCoreBuiltins.d.ts +0 -3
  168. package/dist/utils/scanCoreBuiltins.js +0 -72
  169. package/dist/utils/scanFiles.d.ts +0 -32
  170. package/dist/utils/scanFiles.js +0 -124
  171. package/dist/utils/scanSources.d.ts +0 -10
  172. package/dist/utils/scanSources.js +0 -46
  173. package/dist/utils/sortModules.d.ts +0 -28
  174. package/dist/utils/sortModules.js +0 -105
  175. package/dist/utils/sqlUtil.d.ts +0 -33
  176. package/dist/utils/sqlUtil.js +0 -146
  177. package/dist/utils/util.d.ts +0 -172
  178. package/dist/utils/util.js +0 -517
@@ -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();