Skip to content

Commit 679742f

Browse files
committed
feat: add logging system
1 parent 65ecb46 commit 679742f

7 files changed

Lines changed: 788 additions & 1 deletion

File tree

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
# 日志系统实现指南
2+
3+
自定义日志系统的完整实现。
4+
5+
## 核心实现
6+
7+
### 1. 类型定义
8+
9+
```typescript
10+
// src/logging/types.ts
11+
12+
export type LogLevel = 'debug' | 'info' | 'success' | 'warn' | 'error'
13+
14+
export interface LoggerOptions {
15+
level?: LogLevel
16+
file?: string
17+
timestamp?: boolean
18+
}
19+
20+
export interface LogData {
21+
[key: string]: unknown
22+
}
23+
24+
export interface Logger {
25+
debug: (message: string, data?: LogData) => void
26+
info: (message: string, data?: LogData) => void
27+
success: (message: string, data?: LogData) => void
28+
warn: (message: string, data?: LogData) => void
29+
error: (message: string, error?: Error | LogData) => void
30+
setLevel: (level: LogLevel) => void
31+
}
32+
```
33+
34+
### 2. Logger 实现
35+
36+
```typescript
37+
// src/logging/logger.ts
38+
39+
import type { LogData, Logger, LoggerOptions, LogLevel } from './types'
40+
import { appendFileSync, existsSync, mkdirSync } from 'node:fs'
41+
import { dirname } from 'node:path'
42+
43+
// 日志级别优先级
44+
const LEVEL_ORDER: Record<LogLevel, number> = {
45+
debug: 0,
46+
info: 1,
47+
success: 1,
48+
warn: 2,
49+
error: 3
50+
}
51+
52+
// 终端颜色代码
53+
const COLORS = {
54+
reset: '\x1B[0m', // 重置
55+
dim: '\x1B[2m', // 暗淡(用于时间戳)
56+
debug: '\x1B[36m', // 青色
57+
info: '\x1B[34m', // 蓝色
58+
success: '\x1B[32m', // 绿色
59+
warn: '\x1B[33m', // 黄色
60+
error: '\x1B[31m' // 红色
61+
}
62+
63+
// 格式化时间戳
64+
function formatTime(date: Date): string {
65+
const yyyy = date.getFullYear()
66+
const MM = String(date.getMonth() + 1).padStart(2, '0')
67+
const dd = String(date.getDate()).padStart(2, '0')
68+
const HH = String(date.getHours()).padStart(2, '0')
69+
const mm = String(date.getMinutes()).padStart(2, '0')
70+
const ss = String(date.getSeconds()).padStart(2, '0')
71+
return `${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}`
72+
}
73+
74+
// 填充级别字符串到固定宽度
75+
function padLevel(level: string): string {
76+
return level.padEnd(7)
77+
}
78+
79+
// 创建日志函数
80+
function createLogFunction(
81+
tag: string,
82+
level: LogLevel,
83+
currentLevelRef: { value: LogLevel },
84+
filePath?: string
85+
): (message: string, data?: LogData | Error) => void {
86+
return (message: string, data?: LogData | Error) => {
87+
// 检查日志级别
88+
if (LEVEL_ORDER[level] < LEVEL_ORDER[currentLevelRef.value]) {
89+
return
90+
}
91+
92+
const timestamp = formatTime(new Date())
93+
const tagStr = `[${tag}]`
94+
const levelStr = padLevel(level.toUpperCase())
95+
const dataStr = data ? ` ${JSON.stringify(data)}` : ''
96+
97+
// 控制台输出(带颜色)
98+
const color = COLORS[level]
99+
const consoleMsg = `${COLORS.dim}${timestamp}${COLORS.reset} ${color}${tagStr}${COLORS.reset} ${levelStr} ${message}${dataStr}`
100+
console.log(consoleMsg)
101+
102+
// 文件输出(无颜色)
103+
if (filePath) {
104+
try {
105+
const dir = dirname(filePath)
106+
if (!existsSync(dir)) {
107+
mkdirSync(dir, { recursive: true })
108+
}
109+
const fileMsg = `${timestamp} ${tagStr} ${levelStr} ${message}${dataStr}\n`
110+
appendFileSync(filePath, fileMsg, 'utf-8')
111+
}
112+
catch {
113+
// 静默失败
114+
}
115+
}
116+
}
117+
}
118+
119+
// 创建日志器
120+
export function createLogger(tag: string, options: LoggerOptions = {}): Logger {
121+
const {
122+
level = 'info',
123+
file,
124+
timestamp = true
125+
} = options
126+
127+
const currentLevelRef = { value: level }
128+
129+
return {
130+
debug: createLogFunction(tag, 'debug', currentLevelRef, file),
131+
info: createLogFunction(tag, 'info', currentLevelRef, file),
132+
success: createLogFunction(tag, 'success', currentLevelRef, file),
133+
warn: createLogFunction(tag, 'warn', currentLevelRef, file),
134+
error: createLogFunction(tag, 'error', currentLevelRef, file),
135+
setLevel(newLevel: LogLevel) {
136+
currentLevelRef.value = newLevel
137+
}
138+
}
139+
}
140+
```
141+
142+
### 3. 导出
143+
144+
```typescript
145+
// src/logging/index.ts
146+
147+
export { createLogger } from './logger'
148+
export type { LogData, Logger, LoggerOptions, LogLevel } from './types'
149+
```
150+
151+
### 4. 主入口导出
152+
153+
```typescript
154+
// src/index.ts
155+
156+
export * from './logging'
157+
// ... 其他导出
158+
```
159+
160+
## 设计说明
161+
162+
### 颜色输出
163+
164+
使用 ANSI 转义码实现终端彩色输出:
165+
166+
```typescript
167+
const COLORS = {
168+
reset: '\x1B[0m', // 重置
169+
dim: '\x1B[2m', // 暗淡(用于时间戳)
170+
debug: '\x1B[36m', // 青色
171+
info: '\x1B[34m', // 蓝色
172+
success: '\x1B[32m', // 绿色
173+
warn: '\x1B[33m', // 黄色
174+
error: '\x1B[31m' // 红色
175+
}
176+
```
177+
178+
### 日志级别过滤
179+
180+
使用数值比较实现日志级别过滤:
181+
182+
```typescript
183+
const LEVEL_ORDER: Record<LogLevel, number> = {
184+
debug: 0,
185+
info: 1,
186+
success: 1,
187+
warn: 2,
188+
error: 3
189+
}
190+
191+
// 只有当当前级别 >= 设定级别时才输出
192+
if (LEVEL_ORDER[level] < LEVEL_ORDER[currentLevelRef.value]) {
193+
// 跳过此日志
194+
}
195+
```
196+
197+
### 文件输出
198+
199+
文件输出不包含 ANSI 颜色代码,便于日志解析:
200+
201+
```typescript
202+
// 控制台:带颜色
203+
const consoleMsg = `${COLORS.dim}${timestamp}${COLORS.reset} ...`
204+
205+
// 文件:纯文本
206+
const fileMsg = `${timestamp} ${tagStr} ${levelStr} ${message}${dataStr}\n`
207+
```
208+
209+
## 使用示例
210+
211+
### 为不同模块创建日志器
212+
213+
```typescript
214+
// 为不同模块创建日志器
215+
import { createLogger } from 'frp-bridge'
216+
217+
// src/rpc/logger.ts
218+
export const rpcLogger = createLogger('RPC', {
219+
file: './logs/rpc.log'
220+
})
221+
222+
// src/process/logger.ts
223+
export const processLogger = createLogger('Process', {
224+
file: './logs/process.log'
225+
})
226+
```
227+
228+
### 运行时调整级别
229+
230+
```typescript
231+
const logger = createLogger('MyModule')
232+
233+
// 生产环境只显示警告及以上
234+
logger.setLevel('warn')
235+
236+
// 开发时开启调试
237+
logger.setLevel('debug')
238+
```
239+
240+
### 结构化数据
241+
242+
```typescript
243+
// 使用对象传递结构化数据
244+
logger.info('User logged in', {
245+
userId: 123,
246+
username: 'alice',
247+
ip: '192.168.1.100'
248+
})
249+
```
250+
251+
### 错误处理
252+
253+
```typescript
254+
// 记录 Error 对象
255+
try {
256+
await someOperation()
257+
}
258+
catch (error) {
259+
logger.error('Operation failed', error)
260+
}
261+
```
262+
263+
## 输出格式
264+
265+
### 控制台输出(带颜色)
266+
267+
```
268+
2026-02-05 14:30:45 [RPC] INFO Server started {"port":7000}
269+
2026-02-05 14:30:46 [RPC] SUCCESS Connection established
270+
2026-02-05 14:30:47 [RPC] WARN High memory usage {"usage":"90%"}
271+
2026-02-05 14:30:48 [RPC] ERROR Connection failed
272+
```
273+
274+
### 文件输出(纯文本)
275+
276+
```
277+
2026-02-05 14:30:45 [RPC] INFO Server started {"port":7000}
278+
2026-02-05 14:30:46 [RPC] SUCCESS Connection established
279+
2026-02-05 14:30:47 [RPC] WARN High memory usage {"usage":"90%"}
280+
2026-02-05 14:30:48 [RPC] ERROR Connection failed
281+
```
282+
283+
## 最佳实践
284+
285+
### 1. 使用有意义的标签
286+
287+
```typescript
288+
// 好的标签
289+
const apiLogger = createLogger('API')
290+
const dbLogger = createLogger('Database')
291+
const wsLogger = createLogger('WebSocket')
292+
293+
// 不好的标签
294+
const logger1 = createLogger('log1')
295+
const l1 = createLogger('logger')
296+
```
297+
298+
### 2. 为每个模块创建独立日志器
299+
300+
```typescript
301+
// src/rpc/index.ts
302+
export const logger = createLogger('RPC')
303+
304+
// src/process/index.ts
305+
export const logger = createLogger('Process')
306+
307+
// src/config/index.ts
308+
export const logger = createLogger('Config')
309+
```
310+
311+
### 3. 使用结构化数据
312+
313+
```typescript
314+
// 好的做法
315+
logger.info('Tunnel added', {
316+
name: 'ssh',
317+
type: 'tcp',
318+
localPort: 22,
319+
remotePort: 6000
320+
})
321+
322+
// 不好的做法
323+
logger.info('Tunnel ssh tcp 22 6000 added')
324+
```
325+
326+
### 4. 正确的错误处理
327+
328+
```typescript
329+
// 好的做法
330+
try {
331+
await connect()
332+
}
333+
catch (error) {
334+
logger.error('Connection failed', error)
335+
}
336+
337+
// 不好的做法
338+
try {
339+
await connect()
340+
}
341+
catch (error) {
342+
logger.error('Connection failed') // 丢失了错误信息
343+
}
344+
```
345+
346+
## 环境变量配置
347+
348+
```typescript
349+
const logger = createLogger('Main', {
350+
level: (process.env.LOG_LEVEL as LogLevel) || 'info',
351+
file: process.env.LOG_FILE
352+
})
353+
```

0 commit comments

Comments
 (0)