befly 3.9.12 → 3.9.14
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/docs/README.md +85 -0
- package/docs/addon.md +512 -0
- package/docs/api.md +1604 -0
- package/docs/cipher.md +580 -0
- package/docs/config.md +638 -0
- package/docs/database.md +147 -3
- package/docs/examples.md +892 -0
- package/docs/hook.md +754 -0
- package/docs/logger.md +495 -0
- package/docs/plugin.md +978 -0
- package/docs/quickstart.md +331 -0
- package/docs/sync.md +586 -0
- package/docs/table.md +765 -0
- package/docs/validator.md +618 -0
- package/loader/loadApis.ts +33 -36
- package/package.json +3 -3
- package/sync/syncDb/apply.ts +1 -1
- package/sync/syncDb/constants.ts +0 -11
- package/sync/syncDb/helpers.ts +0 -1
- package/sync/syncDb/table.ts +2 -2
- package/sync/syncDb/tableCreate.ts +3 -3
- package/tests/syncDb-constants.test.ts +1 -23
- package/tests/syncDb-helpers.test.ts +0 -1
- package/types/database.d.ts +0 -2
package/docs/logger.md
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
# Logger 日志系统
|
|
2
|
+
|
|
3
|
+
> 基于 pino 的高性能日志系统
|
|
4
|
+
|
|
5
|
+
## 目录
|
|
6
|
+
|
|
7
|
+
- [概述](#概述)
|
|
8
|
+
- [配置选项](#配置选项)
|
|
9
|
+
- [使用方法](#使用方法)
|
|
10
|
+
- [日志级别](#日志级别)
|
|
11
|
+
- [输出格式](#输出格式)
|
|
12
|
+
- [日志文件](#日志文件)
|
|
13
|
+
- [插件集成](#插件集成)
|
|
14
|
+
- [测试 Mock](#测试-mock)
|
|
15
|
+
- [最佳实践](#最佳实践)
|
|
16
|
+
- [FAQ](#faq)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 概述
|
|
21
|
+
|
|
22
|
+
Logger 是 Befly 的日志系统,基于 [pino](https://github.com/pinojs/pino) 实现:
|
|
23
|
+
|
|
24
|
+
- **高性能**:pino 是 Node.js 最快的日志库之一
|
|
25
|
+
- **文件轮转**:自动按日期分割日志文件
|
|
26
|
+
- **多目标**:同时输出到文件和控制台
|
|
27
|
+
- **延迟初始化**:首次使用时才创建实例
|
|
28
|
+
|
|
29
|
+
**核心规则**:项目中**禁止使用 console**,统一使用 `Logger`。
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 配置选项
|
|
34
|
+
|
|
35
|
+
### LoggerConfig 接口
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
interface LoggerConfig {
|
|
39
|
+
debug?: number; // 是否开启调试模式 (0: 关闭, 1: 开启)
|
|
40
|
+
dir?: string; // 日志目录
|
|
41
|
+
console?: number; // 是否输出到控制台 (0: 关闭, 1: 开启)
|
|
42
|
+
maxSize?: number; // 单个日志文件最大大小 (MB)
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 配置说明
|
|
47
|
+
|
|
48
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
49
|
+
| --------- | ------ | ---------- | -------------------------- |
|
|
50
|
+
| `debug` | number | `0` | 调试模式:0=关闭,1=开启 |
|
|
51
|
+
| `dir` | string | `'./logs'` | 日志文件存放目录 |
|
|
52
|
+
| `console` | number | `1` | 控制台输出:0=关闭,1=开启 |
|
|
53
|
+
| `maxSize` | number | `10` | 单文件最大大小(MB) |
|
|
54
|
+
|
|
55
|
+
### 配置示例
|
|
56
|
+
|
|
57
|
+
在配置文件中设置:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
// befly.dev.json
|
|
61
|
+
{
|
|
62
|
+
"logger": {
|
|
63
|
+
"debug": 1,
|
|
64
|
+
"dir": "./logs",
|
|
65
|
+
"console": 1,
|
|
66
|
+
"maxSize": 10
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
// befly.prod.json
|
|
73
|
+
{
|
|
74
|
+
"logger": {
|
|
75
|
+
"debug": 0,
|
|
76
|
+
"dir": "/var/log/befly",
|
|
77
|
+
"console": 0,
|
|
78
|
+
"maxSize": 50
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 使用方法
|
|
86
|
+
|
|
87
|
+
### 导入 Logger
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { Logger } from '../lib/logger.js';
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 日志方法
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// 信息日志
|
|
97
|
+
Logger.info('用户登录成功');
|
|
98
|
+
Logger.info({ userId: 123 }, '用户登录成功');
|
|
99
|
+
|
|
100
|
+
// 警告日志
|
|
101
|
+
Logger.warn('配置项已弃用');
|
|
102
|
+
Logger.warn({ config: 'oldOption' }, '配置项已弃用');
|
|
103
|
+
|
|
104
|
+
// 错误日志
|
|
105
|
+
Logger.error('数据库连接失败');
|
|
106
|
+
Logger.error({ err: error }, '数据库连接失败');
|
|
107
|
+
|
|
108
|
+
// 调试日志(仅 debug=1 时输出)
|
|
109
|
+
Logger.debug('SQL 查询');
|
|
110
|
+
Logger.debug({ sql: 'SELECT * FROM user', params: [] }, 'SQL 查询');
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 带上下文的日志
|
|
114
|
+
|
|
115
|
+
pino 风格:第一个参数为对象时作为上下文,第二个参数为消息:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// 推荐:上下文 + 消息
|
|
119
|
+
Logger.info({ userId: 123, action: 'login' }, '用户登录成功');
|
|
120
|
+
|
|
121
|
+
// 输出:
|
|
122
|
+
// {"level":30,"time":1234567890,"userId":123,"action":"login","msg":"用户登录成功"}
|
|
123
|
+
|
|
124
|
+
// 记录错误
|
|
125
|
+
try {
|
|
126
|
+
await riskyOperation();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
Logger.error({ err: error }, '操作失败');
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 配置方法
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// 手动配置(通常由插件自动完成)
|
|
136
|
+
Logger.configure({
|
|
137
|
+
debug: 1,
|
|
138
|
+
dir: './custom-logs',
|
|
139
|
+
console: 1,
|
|
140
|
+
maxSize: 20
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 日志级别
|
|
147
|
+
|
|
148
|
+
### 级别定义
|
|
149
|
+
|
|
150
|
+
| 级别 | 数值 | 说明 | 使用场景 |
|
|
151
|
+
| ------- | ---- | -------- | ------------ |
|
|
152
|
+
| `trace` | 10 | 最详细 | 跟踪代码执行 |
|
|
153
|
+
| `debug` | 20 | 调试信息 | 开发调试 |
|
|
154
|
+
| `info` | 30 | 一般信息 | 正常操作记录 |
|
|
155
|
+
| `warn` | 40 | 警告 | 潜在问题 |
|
|
156
|
+
| `error` | 50 | 错误 | 操作失败 |
|
|
157
|
+
| `fatal` | 60 | 致命错误 | 系统崩溃 |
|
|
158
|
+
|
|
159
|
+
### 级别控制
|
|
160
|
+
|
|
161
|
+
- `debug=0`:日志级别为 `info`,只输出 info、warn、error、fatal
|
|
162
|
+
- `debug=1`:日志级别为 `debug`,输出所有级别(debug 及以上)
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// debug=0 时
|
|
166
|
+
Logger.debug('这条不会输出'); // ❌ 被过滤
|
|
167
|
+
Logger.info('这条会输出'); // ✅
|
|
168
|
+
Logger.error('这条会输出'); // ✅
|
|
169
|
+
|
|
170
|
+
// debug=1 时
|
|
171
|
+
Logger.debug('这条会输出'); // ✅
|
|
172
|
+
Logger.info('这条会输出'); // ✅
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 输出格式
|
|
178
|
+
|
|
179
|
+
### JSON 格式
|
|
180
|
+
|
|
181
|
+
日志以 JSON 格式输出,便于日志收集和分析:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"level": 30,
|
|
186
|
+
"time": 1703145600000,
|
|
187
|
+
"pid": 12345,
|
|
188
|
+
"hostname": "server-1",
|
|
189
|
+
"msg": "用户登录成功"
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 带上下文
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"level": 30,
|
|
198
|
+
"time": 1703145600000,
|
|
199
|
+
"userId": 123,
|
|
200
|
+
"email": "user@example.com",
|
|
201
|
+
"msg": "用户登录成功"
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 错误日志
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"level": 50,
|
|
210
|
+
"time": 1703145600000,
|
|
211
|
+
"err": {
|
|
212
|
+
"type": "Error",
|
|
213
|
+
"message": "连接超时",
|
|
214
|
+
"stack": "Error: 连接超时\n at ..."
|
|
215
|
+
},
|
|
216
|
+
"msg": "数据库连接失败"
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 日志文件
|
|
223
|
+
|
|
224
|
+
### 文件命名
|
|
225
|
+
|
|
226
|
+
日志文件按日期命名:
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
logs/
|
|
230
|
+
├── app.2024-01-01.log
|
|
231
|
+
├── app.2024-01-02.log
|
|
232
|
+
├── app.2024-01-03.log
|
|
233
|
+
└── ...
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 文件轮转
|
|
237
|
+
|
|
238
|
+
使用 [pino-roll](https://github.com/flarelabs-net/pino-roll) 实现:
|
|
239
|
+
|
|
240
|
+
- **按日期轮转**:每天创建新文件
|
|
241
|
+
- **按大小轮转**:单文件超过 `maxSize` 时创建新文件
|
|
242
|
+
- **自动创建目录**:目录不存在时自动创建
|
|
243
|
+
|
|
244
|
+
### 配置示例
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// 轮转配置(内部实现)
|
|
248
|
+
{
|
|
249
|
+
target: 'pino-roll',
|
|
250
|
+
level: level,
|
|
251
|
+
options: {
|
|
252
|
+
file: join(config.dir, 'app'), // 基础文件名
|
|
253
|
+
frequency: 'daily', // 按日轮转
|
|
254
|
+
size: `${config.maxSize}m`, // 大小限制
|
|
255
|
+
mkdir: true, // 自动创建目录
|
|
256
|
+
dateFormat: 'yyyy-MM-dd' // 日期格式
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 插件集成
|
|
264
|
+
|
|
265
|
+
### Logger 插件
|
|
266
|
+
|
|
267
|
+
Logger 作为插件自动加载和配置:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// plugins/logger.ts
|
|
271
|
+
const loggerPlugin: Plugin = {
|
|
272
|
+
after: [],
|
|
273
|
+
async handler(): Promise<typeof Logger> {
|
|
274
|
+
if (beflyConfig.logger) {
|
|
275
|
+
Logger.configure(beflyConfig.logger);
|
|
276
|
+
}
|
|
277
|
+
return Logger;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 通过 befly 访问
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// 在 API handler 中
|
|
286
|
+
export default {
|
|
287
|
+
name: '示例接口',
|
|
288
|
+
handler: async (befly, ctx) => {
|
|
289
|
+
befly.logger.info('处理请求');
|
|
290
|
+
befly.logger.debug({ body: ctx.body }, '请求参数');
|
|
291
|
+
return Yes('成功');
|
|
292
|
+
}
|
|
293
|
+
} as ApiRoute;
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### 直接导入
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// 直接导入使用
|
|
300
|
+
import { Logger } from '../lib/logger.js';
|
|
301
|
+
|
|
302
|
+
Logger.info('直接使用');
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## 测试 Mock
|
|
308
|
+
|
|
309
|
+
### 设置 Mock
|
|
310
|
+
|
|
311
|
+
测试时可以设置 Mock 实例:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { Logger, setMockLogger } from '../lib/logger.js';
|
|
315
|
+
import pino from 'pino';
|
|
316
|
+
|
|
317
|
+
// 创建 mock logger
|
|
318
|
+
const mockLogger = pino({ level: 'silent' });
|
|
319
|
+
|
|
320
|
+
// 设置 mock
|
|
321
|
+
setMockLogger(mockLogger);
|
|
322
|
+
|
|
323
|
+
// 测试代码...
|
|
324
|
+
Logger.info('这条日志会被 mock 处理');
|
|
325
|
+
|
|
326
|
+
// 清除 mock
|
|
327
|
+
setMockLogger(null);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 测试示例
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
334
|
+
import { Logger, setMockLogger } from '../lib/logger.js';
|
|
335
|
+
import pino from 'pino';
|
|
336
|
+
|
|
337
|
+
describe('Logger', () => {
|
|
338
|
+
let mockLogger: pino.Logger;
|
|
339
|
+
let infoSpy: any;
|
|
340
|
+
|
|
341
|
+
beforeEach(() => {
|
|
342
|
+
mockLogger = pino({ level: 'debug' });
|
|
343
|
+
infoSpy = vi.spyOn(mockLogger, 'info');
|
|
344
|
+
setMockLogger(mockLogger);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
afterEach(() => {
|
|
348
|
+
setMockLogger(null);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should log info message', () => {
|
|
352
|
+
Logger.info('test message');
|
|
353
|
+
expect(infoSpy).toHaveBeenCalledWith('test message');
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 最佳实践
|
|
361
|
+
|
|
362
|
+
### 1. 使用结构化日志
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// ✅ 推荐:结构化上下文
|
|
366
|
+
Logger.info({ userId: 123, action: 'login', ip: '192.168.1.1' }, '用户登录');
|
|
367
|
+
|
|
368
|
+
// ❌ 避免:字符串拼接
|
|
369
|
+
Logger.info(`用户 ${userId} 从 ${ip} 登录`);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### 2. 错误日志包含堆栈
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
try {
|
|
376
|
+
await riskyOperation();
|
|
377
|
+
} catch (error) {
|
|
378
|
+
// ✅ 使用 err 属性保留完整错误信息
|
|
379
|
+
Logger.error({ err: error }, '操作失败');
|
|
380
|
+
|
|
381
|
+
// ❌ 避免:只记录消息
|
|
382
|
+
Logger.error(`操作失败: ${error.message}`);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### 3. 合理使用日志级别
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// debug: 开发调试信息
|
|
390
|
+
Logger.debug({ sql: query, params: params }, 'SQL 查询');
|
|
391
|
+
|
|
392
|
+
// info: 正常业务操作
|
|
393
|
+
Logger.info({ userId: 123 }, '用户登录成功');
|
|
394
|
+
|
|
395
|
+
// warn: 潜在问题,但不影响功能
|
|
396
|
+
Logger.warn({ config: 'deprecated' }, '配置项已弃用');
|
|
397
|
+
|
|
398
|
+
// error: 操作失败
|
|
399
|
+
Logger.error({ err: error }, '数据库连接失败');
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 4. 不要记录敏感信息
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
// ❌ 避免:记录密码、token 等
|
|
406
|
+
Logger.info({ password: user.password }, '用户信息');
|
|
407
|
+
|
|
408
|
+
// ✅ 推荐:只记录必要信息
|
|
409
|
+
Logger.info({ userId: user.id, email: user.email }, '用户信息');
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### 5. 生产环境关闭控制台
|
|
413
|
+
|
|
414
|
+
```json
|
|
415
|
+
// befly.prod.json
|
|
416
|
+
{
|
|
417
|
+
"logger": {
|
|
418
|
+
"debug": 0,
|
|
419
|
+
"console": 0
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## FAQ
|
|
427
|
+
|
|
428
|
+
### Q: 为什么禁止使用 console?
|
|
429
|
+
|
|
430
|
+
A:
|
|
431
|
+
|
|
432
|
+
1. `console` 没有日志级别控制
|
|
433
|
+
2. `console` 不支持结构化日志
|
|
434
|
+
3. `console` 不支持文件输出
|
|
435
|
+
4. Logger 统一管理便于维护和监控
|
|
436
|
+
|
|
437
|
+
### Q: 日志文件太多怎么办?
|
|
438
|
+
|
|
439
|
+
A: 可以:
|
|
440
|
+
|
|
441
|
+
1. 使用日志清理脚本定期删除旧日志
|
|
442
|
+
2. 配置日志收集系统(如 ELK)后删除本地日志
|
|
443
|
+
3. 使用外部日志服务
|
|
444
|
+
|
|
445
|
+
### Q: 如何查看实时日志?
|
|
446
|
+
|
|
447
|
+
A:
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
# 使用 tail 命令
|
|
451
|
+
tail -f logs/app.2024-01-01.log
|
|
452
|
+
|
|
453
|
+
# 使用 pino-pretty 格式化
|
|
454
|
+
tail -f logs/app.2024-01-01.log | npx pino-pretty
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Q: debug 模式对性能有影响吗?
|
|
458
|
+
|
|
459
|
+
A: pino 的日志过滤非常高效,关闭 debug 时 debug 级别的日志几乎没有性能开销。但在生产环境仍建议关闭 debug。
|
|
460
|
+
|
|
461
|
+
### Q: 如何添加全局上下文?
|
|
462
|
+
|
|
463
|
+
A: 可以创建 child logger:
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
const childLogger = getLogger().child({
|
|
467
|
+
service: 'user-service',
|
|
468
|
+
version: '1.0.0'
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
childLogger.info('所有日志都会包含 service 和 version');
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Q: 日志没有输出怎么排查?
|
|
475
|
+
|
|
476
|
+
A: 检查以下几点:
|
|
477
|
+
|
|
478
|
+
1. 日志级别是否正确(debug 模式是否开启)
|
|
479
|
+
2. 日志目录是否有写入权限
|
|
480
|
+
3. 是否设置了 mock logger
|
|
481
|
+
4. 配置是否正确加载
|
|
482
|
+
|
|
483
|
+
### Q: 如何在测试中捕获日志?
|
|
484
|
+
|
|
485
|
+
A: 使用 `setMockLogger` 设置 mock 实例,然后用 spy 捕获调用:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
const mockLogger = pino({ level: 'debug' });
|
|
489
|
+
const spy = vi.spyOn(mockLogger, 'info');
|
|
490
|
+
setMockLogger(mockLogger);
|
|
491
|
+
|
|
492
|
+
// 执行测试...
|
|
493
|
+
|
|
494
|
+
expect(spy).toHaveBeenCalledWith(expect.objectContaining({ userId: 123 }), expect.any(String));
|
|
495
|
+
```
|