befly 3.76.1 → 3.76.3

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 (149) hide show
  1. package/Befly.js +159 -0
  2. package/README.md +50 -11
  3. package/apis/admin/delete.js +1 -3
  4. package/apis/admin/select.js +2 -7
  5. package/apis/admin/update.js +1 -5
  6. package/apis/api/select.js +2 -7
  7. package/apis/auth/login.js +16 -20
  8. package/apis/dict/delete.js +1 -1
  9. package/apis/dict/detail.js +1 -3
  10. package/apis/dict/select.js +1 -2
  11. package/apis/dict/update.js +1 -1
  12. package/apis/dictType/delete.js +1 -1
  13. package/apis/dictType/detail.js +1 -1
  14. package/apis/dictType/insert.js +1 -1
  15. package/apis/dictType/select.js +2 -7
  16. package/apis/dictType/update.js +2 -2
  17. package/apis/email/config.js +4 -7
  18. package/apis/email/logList.js +2 -7
  19. package/apis/email/send.js +1 -2
  20. package/apis/email/verify.js +1 -3
  21. package/apis/loginLog/select.js +2 -7
  22. package/apis/menu/select.js +2 -7
  23. package/apis/operateLog/select.js +2 -7
  24. package/apis/role/apiSave.js +2 -2
  25. package/apis/role/menuSave.js +2 -2
  26. package/apis/role/select.js +2 -7
  27. package/apis/source/imageList.js +2 -5
  28. package/apis/tongJi/dailyReport.js +12 -13
  29. package/apis/tongJi/dailyReportSelect.js +1 -2
  30. package/apis/tongJi/dailyStats.js +1 -1
  31. package/apis/tongJi/dailyStatsDistribution.js +2 -2
  32. package/apis/tongJi/errorList.js +1 -2
  33. package/apis/tongJi/errorReport.js +1 -2
  34. package/apis/tongJi/projectDelete.js +1 -3
  35. package/apis/tongJi/projectSelect.js +2 -4
  36. package/apis/tongJi/projectUpdate.js +2 -2
  37. package/apis/upload/file.js +7 -5
  38. package/checks/api.js +43 -66
  39. package/checks/config.js +17 -12
  40. package/checks/corns.js +28 -46
  41. package/checks/field.js +18 -0
  42. package/checks/items.js +16 -60
  43. package/checks/menu.js +28 -39
  44. package/checks/table.js +106 -26
  45. package/checks/validation.js +13 -0
  46. package/configs/beflyConfig.json +6 -6
  47. package/configs/constConfig.js +2 -63
  48. package/exports.js +2 -0
  49. package/hooks/auth.js +11 -7
  50. package/hooks/parser.js +55 -59
  51. package/hooks/permission.js +1 -0
  52. package/hooks/rateLimit.js +25 -62
  53. package/hooks/validator.js +43 -31
  54. package/index.js +43 -344
  55. package/{lib → libs}/cacheHelper.js +3 -8
  56. package/libs/cron.js +60 -0
  57. package/libs/logger/index.js +1 -0
  58. package/libs/logger/logger.js +245 -0
  59. package/libs/logger/sanitize.js +115 -0
  60. package/{lib → libs/mysql}/dbHelper.js +100 -73
  61. package/{lib → libs/mysql}/dbParse.js +16 -9
  62. package/{lib → libs/mysql}/dbUtil.js +15 -32
  63. package/libs/mysql/error.js +29 -0
  64. package/libs/mysql/index.js +6 -0
  65. package/libs/mysql/is.js +29 -0
  66. package/libs/mysql/mysql.js +43 -0
  67. package/libs/mysql/sql/identifier.js +85 -0
  68. package/libs/mysql/sql/index.js +2 -0
  69. package/{lib → libs/mysql/sql}/sqlBuilder.js +2 -3
  70. package/libs/mysql/util.js +44 -0
  71. package/libs/redis/index.js +2 -0
  72. package/libs/redis/redis.js +204 -0
  73. package/libs/redis/redisError.js +8 -0
  74. package/libs/tableModel.js +72 -0
  75. package/libs/tool.js +33 -0
  76. package/libs/validator/compiler.js +248 -0
  77. package/libs/validator/index.js +10 -0
  78. package/libs/validator/parser.js +173 -0
  79. package/libs/validator/util.js +5 -0
  80. package/package.json +10 -8
  81. package/paths.js +2 -3
  82. package/plugins/cache.js +2 -1
  83. package/plugins/email.js +4 -4
  84. package/plugins/mysql.js +5 -15
  85. package/plugins/redis.js +4 -12
  86. package/router/api.js +14 -10
  87. package/router/root.js +14 -0
  88. package/router/static.js +3 -3
  89. package/schemas/api.json +99 -0
  90. package/schemas/config.json +255 -0
  91. package/schemas/corn.json +39 -0
  92. package/schemas/item.json +43 -0
  93. package/schemas/menu.json +44 -0
  94. package/schemas/table.json +80 -0
  95. package/scripts/syncDb/apply.js +87 -0
  96. package/scripts/syncDb/ddl.js +43 -0
  97. package/scripts/syncDb/index.js +175 -30
  98. package/scripts/syncDb/model.js +60 -0
  99. package/scripts/syncDb/plan.js +160 -0
  100. package/scripts/syncDb/preflight.js +98 -0
  101. package/scripts/syncDb/report.js +177 -162
  102. package/scripts/syncDb/snapshot.js +118 -0
  103. package/tables/admin.json +51 -53
  104. package/tables/api.json +28 -39
  105. package/tables/dailyReport.json +85 -73
  106. package/tables/dict.json +27 -39
  107. package/tables/dictType.json +20 -34
  108. package/tables/emailLog.json +49 -47
  109. package/tables/errorReport.json +86 -60
  110. package/tables/file.json +47 -50
  111. package/tables/loginLog.json +68 -55
  112. package/tables/menu.json +20 -34
  113. package/tables/operateLog.json +53 -47
  114. package/tables/project.json +18 -30
  115. package/tables/role.json +33 -37
  116. package/utils/cors.js +37 -41
  117. package/utils/deepMerge.js +3 -1
  118. package/utils/error.js +1 -56
  119. package/utils/id.js +44 -0
  120. package/utils/is.js +6 -243
  121. package/utils/response.js +12 -1
  122. package/utils/scanFiles.js +13 -4
  123. package/utils/scanSources.js +58 -20
  124. package/utils/util.js +11 -163
  125. package/apis/_apis.js +0 -20
  126. package/apis/auth/sendSmsCode.js +0 -24
  127. package/configs/regexpAlias.json +0 -55
  128. package/lib/connect.js +0 -119
  129. package/lib/logger.js +0 -430
  130. package/lib/redisHelper.js +0 -399
  131. package/lib/validator.js +0 -274
  132. package/lib/xmlParse.js +0 -149
  133. package/plugins/config.js +0 -13
  134. package/plugins/cron.js +0 -115
  135. package/plugins/logger.js +0 -17
  136. package/plugins/tool.js +0 -131
  137. package/plugins/xmlParse.js +0 -10
  138. package/schemas/config.js +0 -67
  139. package/scripts/syncDb/context.js +0 -120
  140. package/scripts/syncDb/diff.js +0 -279
  141. package/scripts/syncDb/transform.js +0 -113
  142. package/sql/befly.sql +0 -271
  143. package/utils/email.js +0 -3
  144. package/utils/fieldClear.js +0 -22
  145. package/utils/formatValidationIssues.js +0 -28
  146. package/utils/getClientIp.js +0 -47
  147. package/utils/loggerUtils.js +0 -196
  148. package/utils/regexpUtil.js +0 -52
  149. /package/{lib → libs}/smtpText.js +0 -0
@@ -1,55 +0,0 @@
1
- {
2
- "number": "^\\d+$",
3
- "integer": "^-?\\d+$",
4
- "float": "^-?\\d+(\\.\\d+)?$",
5
- "positive": "^[1-9]\\d*$",
6
- "negative": "^-\\d+$",
7
- "zero": "^0$",
8
- "word": "^[a-zA-Z]+$",
9
- "alphanumeric": "^[a-zA-Z0-9]+$",
10
- "alphanumeric_": "^[a-zA-Z0-9_]+$",
11
- "alphanumericDash_": "^[a-zA-Z0-9_-]+$",
12
- "lowercase": "^[a-z]+$",
13
- "uppercase": "^[A-Z]+$",
14
- "chinese": "^[\\u4e00-\\u9fa5]+$",
15
- "chineseWord": "^[\\u4e00-\\u9fa5a-zA-Z]+$",
16
- "email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
17
- "phone": "^1[3-9]\\d{9}$",
18
- "telephone": "^0\\d{2,3}-?\\d{7,8}$",
19
- "url": "^https?://",
20
- "ip": "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$",
21
- "ipv6": "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$",
22
- "domain": "^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$",
23
- "uuid": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
24
- "hex": "^[0-9a-fA-F]+$",
25
- "base64": "^[A-Za-z0-9+/=]+$",
26
- "md5": "^[a-f0-9]{32}$",
27
- "sha1": "^[a-f0-9]{40}$",
28
- "sha256": "^[a-f0-9]{64}$",
29
- "date": "^\\d{4}-\\d{2}-\\d{2}$",
30
- "time": "^\\d{2}:\\d{2}:\\d{2}$",
31
- "datetime": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}",
32
- "year": "^\\d{4}$",
33
- "month": "^(0[1-9]|1[0-2])$",
34
- "day": "^(0[1-9]|[12]\\d|3[01])$",
35
- "variable": "^[a-zA-Z_][a-zA-Z0-9_]*$",
36
- "constant": "^[A-Z][A-Z0-9_]*$",
37
- "package": "^[a-z][a-z0-9-]*$",
38
- "idCard": "^\\d{17}[\\dXx]$",
39
- "passport": "^[a-zA-Z0-9]{5,17}$",
40
- "bankCard": "^\\d{16,19}$",
41
- "wechat": "^[a-zA-Z][a-zA-Z0-9_-]{5,19}$",
42
- "qq": "^[1-9]\\d{4,10}$",
43
- "alipay": "^(1[3-9]\\d{9}|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$",
44
- "username": "^[a-zA-Z][a-zA-Z0-9_]{3,19}$",
45
- "nickname": "^[\\u4e00-\\u9fa5a-zA-Z0-9]{2,20}$",
46
- "passwordWeak": "^.{6,}$",
47
- "passwordMedium": "^(?=.*[a-zA-Z])(?=.*\\d).{8,}$",
48
- "passwordStrong": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*]).{8,}$",
49
- "licensePlate": "^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$",
50
- "postalCode": "^\\d{6}$",
51
- "semver": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.]+)?(\\+[a-zA-Z0-9.]+)?$",
52
- "colorHex": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
53
- "empty": "^$",
54
- "notempty": ".+"
55
- }
package/lib/connect.js DELETED
@@ -1,119 +0,0 @@
1
- /**
2
- * 负责管理数据库和 Redis 连接
3
- */
4
-
5
- import { RedisClient, SQL } from "bun";
6
-
7
- import { createError } from "../utils/error.js";
8
- import { getRunMode } from "../utils/util.js";
9
- import { Logger } from "./logger.js";
10
-
11
- function buildRedisUrl(config) {
12
- if (config.username) {
13
- const encodedUser = encodeURIComponent(config.username);
14
- const encodedPass = encodeURIComponent(config.password || "");
15
- return `redis://${encodedUser}:${encodedPass}@${config.hostname}:${config.port}/${config.db}`;
16
- }
17
-
18
- if (config.password) {
19
- const encodedPass = encodeURIComponent(config.password);
20
- return `redis://:${encodedPass}@${config.hostname}:${config.port}/${config.db}`;
21
- }
22
-
23
- return `redis://${config.hostname}:${config.port}/${config.db}`;
24
- }
25
-
26
- /**
27
- * 连接管理器
28
- */
29
- export class Connect {
30
- static mysqlClient = null;
31
- static redisClient = null;
32
-
33
- /**
34
- * 获取数据库客户端
35
- * @returns 数据库客户端
36
- */
37
- static getMysql() {
38
- return this.mysqlClient;
39
- }
40
-
41
- /**
42
- * 获取 Redis 客户端
43
- * @returns Redis 客户端
44
- */
45
- static getRedis() {
46
- return this.redisClient;
47
- }
48
-
49
- static async handleConnectError(clientKey, label, subsystem, hostname, error) {
50
- await this[clientKey]?.close();
51
- this[clientKey] = null;
52
- throw createError(`${label} 连接失败 (${hostname})`, { cause: error, code: "runtime", subsystem: subsystem, operation: "connect", runMode: getRunMode() });
53
- }
54
-
55
- /**
56
- * 连接数据库
57
- * @param config - 数据库配置
58
- */
59
- static async connectMysql(config) {
60
- try {
61
- this.mysqlClient = new SQL({
62
- adapter: "mysql",
63
- hostname: config.hostname,
64
- port: config.port,
65
- database: config.database,
66
- username: config.username,
67
- password: config.password,
68
- max: config.max,
69
- bigint: true,
70
- idleTimeout: config.idleTimeout,
71
- maxLifetime: config.maxLifetime,
72
- connectionTimeout: config.connectionTimeout,
73
- onclose: (client, err) => {
74
- if (err) {
75
- Logger.warn(`MySQL 连接关闭:${err.message ?? err}`);
76
- }
77
- }
78
- });
79
-
80
- await this.mysqlClient`SELECT 1`;
81
- } catch (error) {
82
- await this.handleConnectError("mysqlClient", "Mysql", "mysql", config.hostname, error);
83
- }
84
- }
85
-
86
- /**
87
- * 连接 Redis
88
- * @param config - Redis 配置
89
- */
90
- static async connectRedis(config) {
91
- try {
92
- this.redisClient = new RedisClient(buildRedisUrl(config));
93
- // Called when disconnected from Redis server
94
- this.redisClient.onclose = (error) => {
95
- if (error) {
96
- Logger.warn(`Redis 断开连接:${error.message ?? error}`);
97
- } else {
98
- Logger.warn("Redis 断开连接");
99
- }
100
- };
101
-
102
- // Eagerly verify the connection before returning
103
- await this.redisClient.ping();
104
- } catch (error) {
105
- await this.handleConnectError("redisClient", "Redis", "redis", config.hostname, error);
106
- }
107
- }
108
-
109
- /**
110
- * 断开连接
111
- */
112
- static async disconnect() {
113
- await this.mysqlClient?.close();
114
- this.mysqlClient = null;
115
-
116
- await this.redisClient?.close();
117
- this.redisClient = null;
118
- }
119
- }
package/lib/logger.js DELETED
@@ -1,430 +0,0 @@
1
- /**
2
- * 日志系统 - Bun 环境自定义实现(替换 pino / pino-roll)
3
- */
4
-
5
- import { createWriteStream, existsSync, mkdirSync, rmSync, statSync } from "node:fs";
6
- import { join as nodePathJoin, resolve as nodePathResolve } from "node:path";
7
-
8
- import { formatYmdHms } from "../utils/datetime.js";
9
- import { createError } from "../utils/error.js";
10
- import { isPlainObject } from "../utils/is.js";
11
- import { buildSensitiveKeyMatcher, sanitizeLogObject } from "../utils/loggerUtils.js";
12
- import { normalizePositiveInt } from "../utils/util.js";
13
-
14
- // 注意:Logger 可能在运行时/测试中被 process.chdir() 影响。
15
- // 为避免相对路径的 logs 目录随着 cwd 变化,使用模块加载时的初始 cwd 作为锚点。
16
- const DEFAULT_MAX_SIZE_MB = 20;
17
- const MAX_FILE_BYTES = DEFAULT_MAX_SIZE_MB * 1024 * 1024;
18
-
19
- 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
-
21
- let sanitizeOptions = {
22
- sanitizeDepth: 5,
23
- sanitizeNodes: 5000,
24
- sanitizeObjectKeys: 500,
25
- sensitiveKeyMatcher: buildSensitiveKeyMatcher({ builtinPatterns: BUILTIN_SENSITIVE_KEYS, userPatterns: [] })
26
- };
27
-
28
- let mockInstance = null;
29
-
30
- let appFileSink = null;
31
- let errorFileSink = null;
32
-
33
- let config = {
34
- debug: 0,
35
- dir: "",
36
- runtimeEnv: "production",
37
- // bm2 多实例标识(BM2_INSTANCE_ID 优先,BM2_APP_INSTANCE 兜底);无 bm2 环境为空串,文件名不带后缀
38
- instanceId: ""
39
- };
40
-
41
- function safeWriteStderr(msg) {
42
- process.stderr.write(`${msg}\n`);
43
- }
44
-
45
- class LogFileSink {
46
- constructor(prefix) {
47
- this.prefix = prefix;
48
-
49
- this.stream = null;
50
- this.streamDate = "";
51
- this.streamIndex = 0;
52
- this.streamSizeBytes = 0;
53
- this.disabled = false;
54
- // 最近一次 write 的完成回调(write 回调按提交顺序触发,等待它即等待全部已提交数据排空)
55
- this.lastWriteDone = null;
56
- }
57
-
58
- write(line) {
59
- if (this.disabled) return;
60
-
61
- this.ensureStreamReady(line.length);
62
- if (this.stream) {
63
- this.streamSizeBytes += line.length;
64
- this.lastWriteDone = new Promise((resolve) => {
65
- this.stream.write(line, resolve);
66
- });
67
- }
68
- }
69
-
70
- /**
71
- * 等待已提交数据排空(不关闭流)
72
- */
73
- async flushNow() {
74
- if (this.lastWriteDone) {
75
- await this.lastWriteDone;
76
- }
77
- }
78
-
79
- async shutdown() {
80
- return this.closeStream();
81
- }
82
-
83
- getFilePath(date, index) {
84
- if (this.prefix === "debug") {
85
- return nodePathJoin(config.dir, "dev.log");
86
- }
87
-
88
- // bm2 多实例时按实例分文件,避免多进程写同一文件(例:app.2026-08-01.0.1.log)
89
- const suffix = index > 0 ? `.${index}` : "";
90
- const instanceSuffix = config.instanceId ? `.${config.instanceId}` : "";
91
- const filename = this.prefix === "app" ? `${date}${instanceSuffix}${suffix}.log` : `${this.prefix}.${date}${instanceSuffix}${suffix}.log`;
92
- return nodePathJoin(config.dir, filename);
93
- }
94
-
95
- ensureStreamReady(nextChunkBytes) {
96
- const date = formatYmdHms(Date.now(), "date");
97
-
98
- if (this.stream && this.streamDate && date !== this.streamDate) {
99
- this.closeStreamSync();
100
- }
101
-
102
- if (!this.stream) {
103
- this.streamDate = date;
104
- this.streamIndex = 0;
105
- this.streamSizeBytes = this.getFileSize(this.getFilePath(date, 0));
106
-
107
- this.openStream(this.getFilePath(date, 0));
108
- }
109
-
110
- if (this.prefix === "debug") {
111
- return;
112
- }
113
-
114
- if (this.stream && this.streamSizeBytes + nextChunkBytes > MAX_FILE_BYTES) {
115
- this.closeStreamSync();
116
- this.streamIndex += 1;
117
- this.streamSizeBytes = 0;
118
-
119
- this.openStream(this.getFilePath(date, this.streamIndex));
120
- }
121
- }
122
-
123
- getFileSize(filePath) {
124
- try {
125
- return statSync(filePath).size;
126
- } catch {
127
- return 0;
128
- }
129
- }
130
-
131
- openStream(filePath) {
132
- try {
133
- this.stream = createWriteStream(filePath, { flags: "a" });
134
- this.stream.on("error", (error) => {
135
- safeWriteStderr(`[Logger] file sink error (${this.prefix}): ${error?.message || error}`);
136
- this.disabled = true;
137
- this.closeStreamSync();
138
- });
139
- } catch (error) {
140
- safeWriteStderr(`[Logger] createWriteStream failed (${this.prefix}): ${error?.message || error}`);
141
- this.disabled = true;
142
- this.stream = null;
143
- }
144
- }
145
-
146
- closeStreamSync() {
147
- if (this.stream) {
148
- this.stream.end();
149
- }
150
- this.stream = null;
151
- this.streamDate = "";
152
- this.streamIndex = 0;
153
- this.streamSizeBytes = 0;
154
- }
155
-
156
- closeStream() {
157
- return new Promise((resolve) => {
158
- const st = this.stream;
159
- if (!st) {
160
- resolve();
161
- return;
162
- }
163
- this.stream = null;
164
- this.streamDate = "";
165
- this.streamIndex = 0;
166
- this.streamSizeBytes = 0;
167
- st.end(() => resolve());
168
- });
169
- }
170
- }
171
-
172
- async function flush() {
173
- if (mockInstance) return;
174
-
175
- for (const sink of [appFileSink, errorFileSink]) {
176
- if (sink) {
177
- await sink.flushNow();
178
- }
179
- }
180
- }
181
-
182
- async function shutdown() {
183
- if (mockInstance) return;
184
-
185
- const currentAppFileSink = appFileSink;
186
- const currentErrorFileSink = errorFileSink;
187
-
188
- for (const sink of [currentAppFileSink, currentErrorFileSink]) {
189
- if (sink) {
190
- await sink.shutdown();
191
- }
192
- }
193
-
194
- if (appFileSink === currentAppFileSink) {
195
- appFileSink = null;
196
- }
197
- if (errorFileSink === currentErrorFileSink) {
198
- errorFileSink = null;
199
- }
200
- }
201
-
202
- function getRuntimeLogDir() {
203
- return nodePathResolve(process.cwd(), "logs");
204
- }
205
-
206
- function prepareLogDir(runtimeEnv, shouldClearDev = false) {
207
- const logDir = getRuntimeLogDir();
208
- const devLogPath = nodePathResolve(logDir, "dev.log");
209
-
210
- try {
211
- if (!existsSync(logDir)) {
212
- mkdirSync(logDir, { recursive: true });
213
- }
214
- if (shouldClearDev && runtimeEnv === "development" && existsSync(devLogPath)) {
215
- rmSync(devLogPath, { force: true });
216
- }
217
- } catch (error) {
218
- throw createError(`创建日志目录失败: ${logDir}. ${error?.message || error}`, { cause: error, code: "runtime" });
219
- }
220
-
221
- return logDir;
222
- }
223
-
224
- function ensureLogDirExists() {
225
- config.dir = prepareLogDir(config.runtimeEnv);
226
- }
227
-
228
- /**
229
- * 配置日志
230
- */
231
- function configure(cfg) {
232
- shutdown();
233
-
234
- config = {
235
- debug: 0,
236
- dir: "",
237
- runtimeEnv: "production",
238
- instanceId: "",
239
- ...cfg
240
- };
241
-
242
- // 实例 ID 来自运行环境(bm2 注入),不允许配置覆盖
243
- config.instanceId = Bun.env.BM2_INSTANCE_ID || Bun.env.BM2_APP_INSTANCE || "";
244
-
245
- config.dir = getRuntimeLogDir();
246
- prepareLogDir(config.runtimeEnv, true);
247
-
248
- appFileSink = null;
249
- errorFileSink = null;
250
-
251
- sanitizeOptions = {
252
- sanitizeDepth: normalizePositiveInt(config.sanitizeDepth, 5, 1, 10),
253
- sanitizeNodes: normalizePositiveInt(config.sanitizeNodes, 5000, 50, 20000),
254
- sanitizeObjectKeys: normalizePositiveInt(config.sanitizeObjectKeys, 500, 10, 5000),
255
- sensitiveKeyMatcher: buildSensitiveKeyMatcher({ builtinPatterns: BUILTIN_SENSITIVE_KEYS, userPatterns: config.excludeFields })
256
- };
257
- }
258
-
259
- /**
260
- * 设置 Mock Logger(用于测试)
261
- * @param mock - Mock Logger 实例(形如 {info/warn/error/debug}),传 null 清除 mock
262
- */
263
- function setMockLogger(mock) {
264
- mockInstance = mock;
265
- }
266
-
267
- function buildJsonLine(level, timeMs, record) {
268
- const out = {
269
- level: level,
270
- time: formatYmdHms(timeMs),
271
- pid: process.pid
272
- };
273
-
274
- if (record && isPlainObject(record)) {
275
- for (const key of Object.keys(record)) {
276
- if (key === "level" || key === "time" || key === "pid") {
277
- continue;
278
- }
279
- out[key] = record[key];
280
- }
281
-
282
- if (out.msg === undefined) {
283
- out.msg = "";
284
- }
285
- } else {
286
- out.msg = "";
287
- }
288
-
289
- try {
290
- return `${JSON.stringify(out)}\n`;
291
- } catch {
292
- return `${JSON.stringify({ level: out.level, time: out.time, pid: out.pid, msg: "[Unserializable log record]" })}\n`;
293
- }
294
- }
295
-
296
- function ensureSinksReady(kind) {
297
- ensureLogDirExists();
298
-
299
- if (config.runtimeEnv === "development") {
300
- if (!appFileSink) {
301
- appFileSink = new LogFileSink("debug");
302
- }
303
- return appFileSink;
304
- }
305
-
306
- if (kind === "app") {
307
- if (!appFileSink) {
308
- appFileSink = new LogFileSink("app");
309
- }
310
- return appFileSink;
311
- }
312
-
313
- if (!errorFileSink) {
314
- errorFileSink = new LogFileSink("error");
315
- }
316
- return errorFileSink;
317
- }
318
-
319
- function writeJsonl(kind, level, record) {
320
- const sink = ensureSinksReady(kind);
321
- const time = Date.now();
322
- const sanitizedRecord = sanitizeLogObject(record, sanitizeOptions);
323
- const fileLine = buildJsonLine(level, time, sanitizedRecord);
324
- sink.write(fileLine);
325
- }
326
-
327
- // 对象清洗/脱敏逻辑已下沉到 utils/loggerUtils.js(减少 logger.js 复杂度)。
328
-
329
- // 日志仅接受 1 个入参(任意类型)。
330
- // - plain object({})直接作为 record
331
- // - 其他任何类型:包装成对象再写入(避免 sink 层依赖入参类型)
332
- function toRecord(input) {
333
- if (isPlainObject(input)) {
334
- return input;
335
- }
336
-
337
- if (input instanceof Error) {
338
- return { err: input, msg: input.message || input.name || "Error" };
339
- }
340
-
341
- if (input === undefined) {
342
- return { msg: "" };
343
- }
344
-
345
- if (input === null) {
346
- return { msg: "null" };
347
- }
348
-
349
- // 非 plain object(数组/Date/Map/...)也走 value 包装,由 sanitize 负责做安全预览/截断。
350
- if (typeof input === "object") {
351
- return { value: input };
352
- }
353
-
354
- try {
355
- return { msg: String(input) };
356
- } catch {
357
- return { msg: "[Unserializable]" };
358
- }
359
- }
360
-
361
- function logWrite(level, input) {
362
- // debug!=1/true 则不记录 debug 日志
363
- if (level === "debug" && config.debug !== 1 && config.debug !== true) return;
364
-
365
- const record0 = toRecord(input);
366
-
367
- // 测试场景:mock logger 走同步写入,并在入口进行清洗/脱敏控制
368
- if (mockInstance) {
369
- const sanitized = sanitizeLogObject(record0, sanitizeOptions);
370
- mockInstance[level](sanitized);
371
- return;
372
- }
373
-
374
- if (level === "error") {
375
- // error 仅写入 error 文件
376
- writeJsonl("error", "error", record0);
377
- } else {
378
- writeJsonl("app", level, record0);
379
- }
380
- }
381
-
382
- function buildLogMethod(level) {
383
- return function (msg, data) {
384
- if (isPlainObject(msg)) {
385
- logWrite(level, msg);
386
- return;
387
- }
388
-
389
- logWrite(level, { msg: msg, data: data });
390
- };
391
- }
392
-
393
- /**
394
- * 日志实例(延迟初始化)
395
- * 方法签名约定:
396
- * - info/warn/debug: (msg, data) —— msg 为字符串或直接传对象(对象时作为完整 record)
397
- * - error: (msg, err, data) —— err 为 Error 或任意附加信息,data 为可选附加数据
398
- * - 输出统一为 JSONL 单行:{ level, time, pid, msg, ...record }
399
- */
400
- export const Logger = {
401
- info: buildLogMethod("info"),
402
- warn: buildLogMethod("warn"),
403
- /**
404
- * 错误日志
405
- * @param msg - 错误说明(字符串;传对象时作为完整 record)
406
- * @param err - 错误对象(Error 实例会被序列化并附带 chain 摘要)或任意附加信息
407
- * @param data - 可选附加数据
408
- */
409
- error: function (msg, err, data) {
410
- if (isPlainObject(msg)) {
411
- logWrite("error", msg);
412
- return;
413
- }
414
-
415
- logWrite("error", { msg: msg, err: err, data: data });
416
- },
417
- debug: buildLogMethod("debug"),
418
- flush: async function () {
419
- await flush();
420
- },
421
- configure: function (cfg) {
422
- configure(cfg);
423
- },
424
- setMock: function (mock) {
425
- setMockLogger(mock);
426
- },
427
- shutdown: async function () {
428
- await shutdown();
429
- }
430
- };