ops-toolkit 1.2.0 → 1.2.2
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/.env.example +9 -0
- package/.release-it.json +4 -2
- package/AGENTS.md +13 -9
- package/CHANGELOG.md +10 -1
- package/README.md +258 -31
- package/bin/ops-toolkit.ts +4 -87
- package/dist/bin/ops-toolkit.js +11416 -0
- package/dist/index.js +10673 -2983
- package/docs/API.md +850 -0
- package/docs/ARCHITECTURE.md +433 -0
- package/docs/DEVELOPMENT.md +554 -0
- package/docs/PUBLISH.md +172 -0
- package/package.json +11 -8
- package/src/cli/app.ts +188 -0
- package/src/cli/command-discovery.ts +212 -0
- package/src/cli/command-registry.ts +136 -0
- package/src/commands/monitor/index.ts +199 -58
- package/src/index.ts +4 -77
- package/src/types/ui.ts +3 -3
- package/src/utils/config.ts +385 -64
- package/src/utils/error-handlers.ts +94 -0
- package/src/utils/error-reporter.ts +234 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/logger.ts +418 -22
- package/src/utils/system.ts +26 -3
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { Logger } from './logger';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 错误严重程度
|
|
6
|
+
*/
|
|
7
|
+
export enum ErrorSeverity {
|
|
8
|
+
LOW = 'low',
|
|
9
|
+
MEDIUM = 'medium',
|
|
10
|
+
HIGH = 'high',
|
|
11
|
+
CRITICAL = 'critical',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 错误上下文信息
|
|
16
|
+
*/
|
|
17
|
+
export interface ErrorContext {
|
|
18
|
+
command?: string;
|
|
19
|
+
action?: string;
|
|
20
|
+
userId?: string;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
timestamp?: string;
|
|
23
|
+
additionalInfo?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 标准化错误报告
|
|
28
|
+
*/
|
|
29
|
+
export interface ErrorReport {
|
|
30
|
+
id: string;
|
|
31
|
+
code: string;
|
|
32
|
+
message: string;
|
|
33
|
+
severity: ErrorSeverity;
|
|
34
|
+
context?: ErrorContext;
|
|
35
|
+
originalError?: Error;
|
|
36
|
+
stack?: string;
|
|
37
|
+
timestamp: string;
|
|
38
|
+
resolved: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 错误报告器
|
|
43
|
+
*/
|
|
44
|
+
export class ErrorReporter {
|
|
45
|
+
private static reports: Map<string, ErrorReport> = new Map();
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 创建错误报告
|
|
49
|
+
*/
|
|
50
|
+
static createReport(
|
|
51
|
+
code: string,
|
|
52
|
+
message: string,
|
|
53
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
|
|
54
|
+
context?: ErrorContext,
|
|
55
|
+
originalError?: Error
|
|
56
|
+
): ErrorReport {
|
|
57
|
+
const report: ErrorReport = {
|
|
58
|
+
id: this.generateId(),
|
|
59
|
+
code,
|
|
60
|
+
message,
|
|
61
|
+
severity,
|
|
62
|
+
context: {
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
...context,
|
|
65
|
+
},
|
|
66
|
+
originalError,
|
|
67
|
+
stack: originalError?.stack,
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
resolved: false,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
this.reports.set(report.id, report);
|
|
73
|
+
return report;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 报告错误
|
|
78
|
+
*/
|
|
79
|
+
static report(report: ErrorReport): void {
|
|
80
|
+
const severityColor = this.getSeverityColor(report.severity);
|
|
81
|
+
const icon = this.getSeverityIcon(report.severity);
|
|
82
|
+
|
|
83
|
+
console.error(chalk.red(`${icon} 错误报告 [${report.id}]`));
|
|
84
|
+
console.error(chalk.red(` 代码: ${report.code}`));
|
|
85
|
+
console.error(chalk.red(` 消息: ${report.message}`));
|
|
86
|
+
console.error(severityColor(` 严重程度: ${report.severity.toUpperCase()}`));
|
|
87
|
+
console.error(chalk.gray(` 时间: ${report.timestamp}`));
|
|
88
|
+
|
|
89
|
+
if (report.context) {
|
|
90
|
+
console.error(chalk.cyan(' 上下文:'));
|
|
91
|
+
Object.entries(report.context).forEach(([key, value]) => {
|
|
92
|
+
if (key !== 'timestamp') {
|
|
93
|
+
console.error(chalk.cyan(` ${key}: ${value}`));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (report.originalError) {
|
|
99
|
+
console.error(chalk.yellow(' 原始错误:'));
|
|
100
|
+
console.error(chalk.yellow(` ${report.originalError.message}`));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (process.env.DEBUG && report.stack) {
|
|
104
|
+
console.error(chalk.gray(' 堆栈跟踪:'));
|
|
105
|
+
console.error(chalk.gray(report.stack));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 记录到日志
|
|
109
|
+
Logger.error(`错误报告 [${report.id}]: ${report.code} - ${report.message}`, report);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 获取错误严重程度的颜色
|
|
114
|
+
*/
|
|
115
|
+
private static getSeverityColor(severity: ErrorSeverity): (text: string) => string {
|
|
116
|
+
switch (severity) {
|
|
117
|
+
case ErrorSeverity.LOW:
|
|
118
|
+
return chalk.blue;
|
|
119
|
+
case ErrorSeverity.MEDIUM:
|
|
120
|
+
return chalk.yellow;
|
|
121
|
+
case ErrorSeverity.HIGH:
|
|
122
|
+
return chalk.red;
|
|
123
|
+
case ErrorSeverity.CRITICAL:
|
|
124
|
+
return chalk.magenta;
|
|
125
|
+
default:
|
|
126
|
+
return chalk.white;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 获取错误严重程度的图标
|
|
132
|
+
*/
|
|
133
|
+
private static getSeverityIcon(severity: ErrorSeverity): string {
|
|
134
|
+
switch (severity) {
|
|
135
|
+
case ErrorSeverity.LOW:
|
|
136
|
+
return '💡';
|
|
137
|
+
case ErrorSeverity.MEDIUM:
|
|
138
|
+
return '⚠️';
|
|
139
|
+
case ErrorSeverity.HIGH:
|
|
140
|
+
return '❌';
|
|
141
|
+
case ErrorSeverity.CRITICAL:
|
|
142
|
+
return '🔥';
|
|
143
|
+
default:
|
|
144
|
+
return '❓';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 生成唯一ID
|
|
150
|
+
*/
|
|
151
|
+
private static generateId(): string {
|
|
152
|
+
return `ERR_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 获取所有错误报告
|
|
157
|
+
*/
|
|
158
|
+
static getAllReports(): ErrorReport[] {
|
|
159
|
+
return Array.from(this.reports.values());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 根据ID获取错误报告
|
|
164
|
+
*/
|
|
165
|
+
static getReport(id: string): ErrorReport | undefined {
|
|
166
|
+
return this.reports.get(id);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 标记错误为已解决
|
|
171
|
+
*/
|
|
172
|
+
static resolveReport(id: string): boolean {
|
|
173
|
+
const report = this.reports.get(id);
|
|
174
|
+
if (report) {
|
|
175
|
+
report.resolved = true;
|
|
176
|
+
Logger.info(`错误报告已解决: ${id}`);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 清理已解决的错误报告
|
|
184
|
+
*/
|
|
185
|
+
static clearResolvedReports(): void {
|
|
186
|
+
const unresolved = Array.from(this.reports.entries()).filter(([, report]) => !report.resolved);
|
|
187
|
+
|
|
188
|
+
this.reports = new Map(unresolved);
|
|
189
|
+
Logger.info('已清理解决的错误报告');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 错误处理中间件
|
|
195
|
+
*/
|
|
196
|
+
export function errorHandler(
|
|
197
|
+
error: Error,
|
|
198
|
+
context: ErrorContext = {},
|
|
199
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM
|
|
200
|
+
): void {
|
|
201
|
+
const report = ErrorReporter.createReport(
|
|
202
|
+
'UNKNOWN_ERROR',
|
|
203
|
+
error.message,
|
|
204
|
+
severity,
|
|
205
|
+
context,
|
|
206
|
+
error
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
ErrorReporter.report(report);
|
|
210
|
+
|
|
211
|
+
// 根据严重程度决定是否退出程序
|
|
212
|
+
if (severity === ErrorSeverity.CRITICAL) {
|
|
213
|
+
Logger.error('严重错误,程序即将退出');
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 异步错误处理包装器
|
|
220
|
+
*/
|
|
221
|
+
export function withAsyncErrorHandling<T extends unknown[]>(
|
|
222
|
+
fn: (...args: T) => Promise<void>,
|
|
223
|
+
context: ErrorContext = {},
|
|
224
|
+
severity: ErrorSeverity = ErrorSeverity.MEDIUM
|
|
225
|
+
) {
|
|
226
|
+
return async (...args: T): Promise<void> => {
|
|
227
|
+
try {
|
|
228
|
+
await fn(...args);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
231
|
+
errorHandler(err, context, severity);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
package/src/utils/index.ts
CHANGED
package/src/utils/logger.ts
CHANGED
|
@@ -1,47 +1,386 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import figlet from 'figlet';
|
|
3
3
|
import boxen from 'boxen';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import type { ErrorReport } from './error-reporter';
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
/**
|
|
10
|
+
* 日志级别
|
|
11
|
+
*/
|
|
12
|
+
export enum LogLevel {
|
|
13
|
+
DEBUG = 0,
|
|
14
|
+
INFO = 1,
|
|
15
|
+
WARN = 2,
|
|
16
|
+
ERROR = 3,
|
|
17
|
+
CRITICAL = 4,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 日志条目
|
|
22
|
+
*/
|
|
23
|
+
export interface LogEntry {
|
|
24
|
+
timestamp: string;
|
|
25
|
+
level: LogLevel;
|
|
26
|
+
message: string;
|
|
27
|
+
context?: Record<string, unknown>;
|
|
28
|
+
error?: Error;
|
|
29
|
+
module?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 日志配置
|
|
34
|
+
*/
|
|
35
|
+
export interface LogConfig {
|
|
36
|
+
level: LogLevel;
|
|
37
|
+
enableFileLogging: boolean;
|
|
38
|
+
logDirectory: string;
|
|
39
|
+
maxFileSize: number;
|
|
40
|
+
maxFiles: number;
|
|
41
|
+
enableConsoleColors: boolean;
|
|
42
|
+
enableStructuredOutput: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 增强的日志工具类
|
|
47
|
+
*/
|
|
6
48
|
export class Logger {
|
|
7
|
-
private static
|
|
8
|
-
|
|
9
|
-
|
|
49
|
+
private static config: LogConfig = {
|
|
50
|
+
level: LogLevel.INFO,
|
|
51
|
+
enableFileLogging: false,
|
|
52
|
+
logDirectory: path.join(os.homedir(), '.ops-toolkit', 'logs'),
|
|
53
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
54
|
+
maxFiles: 5,
|
|
55
|
+
enableConsoleColors: true,
|
|
56
|
+
enableStructuredOutput: false,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
private static logBuffer: LogEntry[] = [];
|
|
60
|
+
private static maxBufferSize = 1000;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 配置日志系统
|
|
64
|
+
*/
|
|
65
|
+
static configure(config: Partial<LogConfig>): void {
|
|
66
|
+
this.config = { ...this.config, ...config };
|
|
67
|
+
|
|
68
|
+
if (this.config.enableFileLogging) {
|
|
69
|
+
this.ensureLogDirectory();
|
|
70
|
+
}
|
|
10
71
|
}
|
|
11
72
|
|
|
12
|
-
|
|
13
|
-
|
|
73
|
+
/**
|
|
74
|
+
* 确保日志目录存在
|
|
75
|
+
*/
|
|
76
|
+
private static ensureLogDirectory(): void {
|
|
77
|
+
if (!fs.existsSync(this.config.logDirectory)) {
|
|
78
|
+
fs.mkdirSync(this.config.logDirectory, { recursive: true });
|
|
79
|
+
}
|
|
14
80
|
}
|
|
15
81
|
|
|
16
|
-
|
|
17
|
-
|
|
82
|
+
/**
|
|
83
|
+
* 获取日志级别名称
|
|
84
|
+
*/
|
|
85
|
+
private static getLevelName(level: LogLevel): string {
|
|
86
|
+
switch (level) {
|
|
87
|
+
case LogLevel.DEBUG:
|
|
88
|
+
return 'DEBUG';
|
|
89
|
+
case LogLevel.INFO:
|
|
90
|
+
return 'INFO';
|
|
91
|
+
case LogLevel.WARN:
|
|
92
|
+
return 'WARN';
|
|
93
|
+
case LogLevel.ERROR:
|
|
94
|
+
return 'ERROR';
|
|
95
|
+
case LogLevel.CRITICAL:
|
|
96
|
+
return 'CRITICAL';
|
|
97
|
+
default:
|
|
98
|
+
return 'UNKNOWN';
|
|
99
|
+
}
|
|
18
100
|
}
|
|
19
101
|
|
|
20
|
-
|
|
21
|
-
|
|
102
|
+
/**
|
|
103
|
+
* 获取日志级别颜色
|
|
104
|
+
*/
|
|
105
|
+
private static getLevelColor(level: LogLevel): (text: string) => string {
|
|
106
|
+
if (!this.config.enableConsoleColors) {
|
|
107
|
+
return (text: string) => text;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
switch (level) {
|
|
111
|
+
case LogLevel.DEBUG:
|
|
112
|
+
return chalk.gray;
|
|
113
|
+
case LogLevel.INFO:
|
|
114
|
+
return chalk.blue;
|
|
115
|
+
case LogLevel.WARN:
|
|
116
|
+
return chalk.yellow;
|
|
117
|
+
case LogLevel.ERROR:
|
|
118
|
+
return chalk.red;
|
|
119
|
+
case LogLevel.CRITICAL:
|
|
120
|
+
return chalk.magenta;
|
|
121
|
+
default:
|
|
122
|
+
return chalk.white;
|
|
123
|
+
}
|
|
22
124
|
}
|
|
23
125
|
|
|
24
|
-
|
|
25
|
-
|
|
126
|
+
/**
|
|
127
|
+
* 格式化控制台消息
|
|
128
|
+
*/
|
|
129
|
+
private static formatConsoleMessage(entry: LogEntry): string {
|
|
130
|
+
const levelName = this.getLevelName(entry.level);
|
|
131
|
+
const color = this.getLevelColor(entry.level);
|
|
132
|
+
const timestamp = entry.timestamp;
|
|
133
|
+
|
|
134
|
+
let message = `${color(`[${timestamp}] [${levelName}]`)} ${entry.message}`;
|
|
135
|
+
|
|
136
|
+
if (entry.module) {
|
|
137
|
+
message += chalk.gray(` (${entry.module})`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
141
|
+
const contextStr = Object.entries(entry.context)
|
|
142
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
143
|
+
.join(', ');
|
|
144
|
+
message += chalk.gray(` [${contextStr}]`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return message;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 格式化文件日志消息(JSON格式)
|
|
152
|
+
*/
|
|
153
|
+
private static formatFileMessage(entry: LogEntry): string {
|
|
154
|
+
return JSON.stringify(entry, null, 0);
|
|
26
155
|
}
|
|
27
156
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
157
|
+
/**
|
|
158
|
+
* 记录日志条目
|
|
159
|
+
*/
|
|
160
|
+
private static logEntry(entry: LogEntry): void {
|
|
161
|
+
if (entry.level < this.config.level) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 控制台输出
|
|
166
|
+
console.log(this.formatConsoleMessage(entry));
|
|
167
|
+
|
|
168
|
+
// 文件输出
|
|
169
|
+
if (this.config.enableFileLogging) {
|
|
170
|
+
this.writeToFile(entry);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 添加到缓冲区
|
|
174
|
+
this.logBuffer.push(entry);
|
|
175
|
+
if (this.logBuffer.length > this.maxBufferSize) {
|
|
176
|
+
this.logBuffer.shift();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 如果是严重错误,额外处理
|
|
180
|
+
if (entry.level >= LogLevel.ERROR) {
|
|
181
|
+
if (entry.error) {
|
|
182
|
+
this.handleSeriousError(entry);
|
|
183
|
+
}
|
|
31
184
|
}
|
|
32
185
|
}
|
|
33
186
|
|
|
34
|
-
|
|
187
|
+
/**
|
|
188
|
+
* 写入日志文件
|
|
189
|
+
*/
|
|
190
|
+
private static writeToFile(entry: LogEntry): void {
|
|
191
|
+
try {
|
|
192
|
+
const logFile = path.join(
|
|
193
|
+
this.config.logDirectory,
|
|
194
|
+
`ops-toolkit-${this.getDateString()}.log`
|
|
195
|
+
);
|
|
196
|
+
const message = this.formatFileMessage(entry) + '\n';
|
|
197
|
+
|
|
198
|
+
// 检查文件大小
|
|
199
|
+
if (fs.existsSync(logFile)) {
|
|
200
|
+
const stats = fs.statSync(logFile);
|
|
201
|
+
if (stats.size > this.config.maxFileSize) {
|
|
202
|
+
this.rotateLogFile(logFile);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fs.appendFileSync(logFile, message, 'utf8');
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error('写入日志文件失败:', error);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 轮转日志文件
|
|
214
|
+
*/
|
|
215
|
+
private static rotateLogFile(logFile: string): void {
|
|
216
|
+
const baseName = logFile.replace(/\.log$/, '');
|
|
217
|
+
|
|
218
|
+
// 删除最老的日志文件
|
|
219
|
+
const oldestFile = `${baseName}-${this.config.maxFiles}.log`;
|
|
220
|
+
if (fs.existsSync(oldestFile)) {
|
|
221
|
+
fs.unlinkSync(oldestFile);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 轮转现有文件
|
|
225
|
+
for (let i = this.config.maxFiles - 1; i >= 1; i--) {
|
|
226
|
+
const currentFile = `${baseName}-${i}.log`;
|
|
227
|
+
const nextFile = `${baseName}-${i + 1}.log`;
|
|
228
|
+
if (fs.existsSync(currentFile)) {
|
|
229
|
+
fs.renameSync(currentFile, nextFile);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 移动当前文件
|
|
234
|
+
const firstRotatedFile = `${baseName}-1.log`;
|
|
235
|
+
if (fs.existsSync(logFile)) {
|
|
236
|
+
fs.renameSync(logFile, firstRotatedFile);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 获取日期字符串
|
|
242
|
+
*/
|
|
243
|
+
private static getDateString(): string {
|
|
244
|
+
const dateStr = new Date().toISOString().split('T')[0];
|
|
245
|
+
return dateStr || new Date().toISOString().slice(0, 10);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 处理严重错误
|
|
250
|
+
*/
|
|
251
|
+
private static handleSeriousError(entry: LogEntry): void {
|
|
252
|
+
if (entry.error) {
|
|
253
|
+
const errorReport: Partial<ErrorReport> = {
|
|
254
|
+
code: 'LOG_ERROR',
|
|
255
|
+
message: entry.message,
|
|
256
|
+
originalError: entry.error,
|
|
257
|
+
context: entry.context,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// 这里可以集成错误报告系统
|
|
261
|
+
console.error('严重错误已记录:', errorReport);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 调试日志
|
|
267
|
+
*/
|
|
268
|
+
static debug(message: string, context?: Record<string, unknown>, module?: string): void {
|
|
269
|
+
this.logEntry({
|
|
270
|
+
timestamp: new Date().toISOString(),
|
|
271
|
+
level: LogLevel.DEBUG,
|
|
272
|
+
message,
|
|
273
|
+
context,
|
|
274
|
+
module,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 信息日志
|
|
280
|
+
*/
|
|
281
|
+
static info(message: string, context?: Record<string, unknown>, module?: string): void {
|
|
282
|
+
this.logEntry({
|
|
283
|
+
timestamp: new Date().toISOString(),
|
|
284
|
+
level: LogLevel.INFO,
|
|
285
|
+
message,
|
|
286
|
+
context,
|
|
287
|
+
module,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 警告日志
|
|
293
|
+
*/
|
|
294
|
+
static warn(message: string, context?: Record<string, unknown>, module?: string): void {
|
|
295
|
+
this.logEntry({
|
|
296
|
+
timestamp: new Date().toISOString(),
|
|
297
|
+
level: LogLevel.WARN,
|
|
298
|
+
message,
|
|
299
|
+
context,
|
|
300
|
+
module,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* 警告日志(别名)
|
|
306
|
+
*/
|
|
307
|
+
static warning(message: string, context?: Record<string, unknown>, module?: string): void {
|
|
308
|
+
this.warn(message, context, module);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 错误日志
|
|
313
|
+
*/
|
|
314
|
+
static error(
|
|
315
|
+
message: string,
|
|
316
|
+
error?: Error | unknown,
|
|
317
|
+
context?: Record<string, unknown>,
|
|
318
|
+
module?: string
|
|
319
|
+
): void {
|
|
320
|
+
const err = error instanceof Error ? error : undefined;
|
|
321
|
+
this.logEntry({
|
|
322
|
+
timestamp: new Date().toISOString(),
|
|
323
|
+
level: LogLevel.ERROR,
|
|
324
|
+
message,
|
|
325
|
+
error: err,
|
|
326
|
+
context: error && !err ? { error: String(error) } : context,
|
|
327
|
+
module,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 严重错误日志
|
|
333
|
+
*/
|
|
334
|
+
static critical(
|
|
335
|
+
message: string,
|
|
336
|
+
error?: Error | unknown,
|
|
337
|
+
context?: Record<string, unknown>,
|
|
338
|
+
module?: string
|
|
339
|
+
): void {
|
|
340
|
+
const err = error instanceof Error ? error : undefined;
|
|
341
|
+
this.logEntry({
|
|
342
|
+
timestamp: new Date().toISOString(),
|
|
343
|
+
level: LogLevel.CRITICAL,
|
|
344
|
+
message,
|
|
345
|
+
error: err,
|
|
346
|
+
context: error && !err ? { error: String(error) } : context,
|
|
347
|
+
module,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* 成功日志(info的别名,绿色显示)
|
|
353
|
+
*/
|
|
354
|
+
static success(message: string, context?: Record<string, unknown>, module?: string): void {
|
|
355
|
+
if (this.config.enableConsoleColors) {
|
|
356
|
+
const successMessage = chalk.green(message);
|
|
357
|
+
this.logEntry({
|
|
358
|
+
timestamp: new Date().toISOString(),
|
|
359
|
+
level: LogLevel.INFO,
|
|
360
|
+
message: `✅ ${successMessage}`,
|
|
361
|
+
context,
|
|
362
|
+
module,
|
|
363
|
+
});
|
|
364
|
+
} else {
|
|
365
|
+
this.info(`✅ ${message}`, context, module);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 显示标题
|
|
371
|
+
*/
|
|
35
372
|
static showTitle(text: string, font: string = 'Standard'): void {
|
|
36
373
|
console.log(chalk.cyan(figlet.textSync(text, { font })));
|
|
37
374
|
}
|
|
38
375
|
|
|
39
|
-
|
|
40
|
-
|
|
376
|
+
/**
|
|
377
|
+
* 显示框
|
|
378
|
+
*/
|
|
379
|
+
static showBox(content: string, options?: Record<string, unknown>): void {
|
|
41
380
|
const boxOptions = {
|
|
42
381
|
padding: 1,
|
|
43
382
|
margin: 1,
|
|
44
|
-
borderStyle: 'round',
|
|
383
|
+
borderStyle: 'round' as const,
|
|
45
384
|
borderColor: 'cyan',
|
|
46
385
|
...options,
|
|
47
386
|
};
|
|
@@ -49,14 +388,71 @@ export class Logger {
|
|
|
49
388
|
console.log(boxen(content, boxOptions));
|
|
50
389
|
}
|
|
51
390
|
|
|
52
|
-
|
|
391
|
+
/**
|
|
392
|
+
* 显示分隔线
|
|
393
|
+
*/
|
|
53
394
|
static separator(char: string = '-', length: number = 50): void {
|
|
54
395
|
console.log(chalk.gray(char.repeat(length)));
|
|
55
396
|
}
|
|
56
397
|
|
|
57
|
-
|
|
58
|
-
|
|
398
|
+
/**
|
|
399
|
+
* 显示加载动画
|
|
400
|
+
*/
|
|
401
|
+
static spinner(message: string): {
|
|
402
|
+
start: () => void;
|
|
403
|
+
stop: () => void;
|
|
404
|
+
succeed: (text?: string) => void;
|
|
405
|
+
fail: (text?: string) => void;
|
|
406
|
+
warn: (text?: string) => void;
|
|
407
|
+
info: (text?: string) => void;
|
|
408
|
+
} {
|
|
59
409
|
const ora = require('ora');
|
|
60
410
|
return ora(message).start();
|
|
61
411
|
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 获取日志缓冲区
|
|
415
|
+
*/
|
|
416
|
+
static getLogBuffer(): LogEntry[] {
|
|
417
|
+
return [...this.logBuffer];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* 清空日志缓冲区
|
|
422
|
+
*/
|
|
423
|
+
static clearLogBuffer(): void {
|
|
424
|
+
this.logBuffer = [];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* 设置日志级别
|
|
429
|
+
*/
|
|
430
|
+
static setLevel(level: LogLevel): void {
|
|
431
|
+
this.config.level = level;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* 启用文件日志
|
|
436
|
+
*/
|
|
437
|
+
static enableFileLogging(directory?: string): void {
|
|
438
|
+
this.config.enableFileLogging = true;
|
|
439
|
+
if (directory) {
|
|
440
|
+
this.config.logDirectory = directory;
|
|
441
|
+
}
|
|
442
|
+
this.ensureLogDirectory();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* 禁用文件日志
|
|
447
|
+
*/
|
|
448
|
+
static disableFileLogging(): void {
|
|
449
|
+
this.config.enableFileLogging = false;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 获取当前配置
|
|
454
|
+
*/
|
|
455
|
+
static getConfig(): LogConfig {
|
|
456
|
+
return { ...this.config };
|
|
457
|
+
}
|
|
62
458
|
}
|