rl-rockcli 0.0.1
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/LICENSE +21 -0
- package/bin/rockcli.js +40 -0
- package/package.json +62 -0
- package/src/core/commands/attach/index.js +242 -0
- package/src/core/commands/log/constants.js +20 -0
- package/src/core/commands/log/index.js +94 -0
- package/src/core/commands/log/search.js +106 -0
- package/src/core/commands/sandbox/index.js +428 -0
- package/src/core/config/index.js +77 -0
- package/src/core/display/constants.js +59 -0
- package/src/core/display/format.js +178 -0
- package/src/core/display/highlight.js +34 -0
- package/src/core/index.js +55 -0
- package/src/core/providers/index.js +9 -0
- package/src/core/providers/log-provider.js +79 -0
- package/src/core/sdks/sandbox/client.js +472 -0
- package/src/core/sdks/sandbox/config.js +57 -0
- package/src/core/sdks/sandbox/index.js +13 -0
- package/src/core/sdks/sandbox/types.js +5 -0
- package/src/core/utils/index.js +9 -0
- package/src/core/utils/logger.js +106 -0
- package/src/core/utils/time.js +52 -0
- package/src/plugins/oss-file-log/file-client.js +186 -0
- package/src/plugins/oss-file-log/index.js +18 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志格式化输出工具
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { DISPLAY_FIELDS, FIELD_COLOR_MAP, DEFAULT_FIELD_COLOR, TRUNCATE_FIELDS, ANSI_COLORS } = require('./constants');
|
|
6
|
+
const { highlightKeyword } = require('./highlight');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 截断过长的字段值
|
|
10
|
+
* @param {string} value - 字段值
|
|
11
|
+
* @param {string} fieldName - 字段名
|
|
12
|
+
* @param {number} truncate - 截断长度(0 表示不截断)
|
|
13
|
+
* @returns {string} 截断后的值
|
|
14
|
+
*/
|
|
15
|
+
function truncateValue(value, fieldName, truncate) {
|
|
16
|
+
if (!truncate || truncate === 0) return value;
|
|
17
|
+
if (!TRUNCATE_FIELDS.includes(fieldName)) return value;
|
|
18
|
+
|
|
19
|
+
const str = String(value);
|
|
20
|
+
if (str.length <= truncate) return str;
|
|
21
|
+
return `${str.substring(0, truncate)}...[truncated]`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 应用高亮和截断
|
|
26
|
+
* @param {*} value - 字段值
|
|
27
|
+
* @param {string} fieldName - 字段名
|
|
28
|
+
* @param {number} truncate - 截断长度
|
|
29
|
+
* @param {string|string[]} keyword - 关键字
|
|
30
|
+
* @param {boolean} highlight - 是否高亮
|
|
31
|
+
* @returns {string} 处理后的值
|
|
32
|
+
*/
|
|
33
|
+
function applyHighlightAndTruncate(value, fieldName, truncate, keyword, highlight) {
|
|
34
|
+
let str = typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
35
|
+
str = truncateValue(str, fieldName, truncate);
|
|
36
|
+
if (keyword && highlight) {
|
|
37
|
+
str = highlightKeyword(str, keyword);
|
|
38
|
+
}
|
|
39
|
+
return str;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 获取字段颜色
|
|
44
|
+
* @param {string} fieldName - 字段名
|
|
45
|
+
* @param {boolean} isTTY - 是否在终端环境
|
|
46
|
+
* @returns {string} ANSI 颜色代码
|
|
47
|
+
*/
|
|
48
|
+
function getFieldColor(fieldName, isTTY) {
|
|
49
|
+
if (!isTTY) return '';
|
|
50
|
+
return FIELD_COLOR_MAP[fieldName] || DEFAULT_FIELD_COLOR;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 按字段名过滤日志对象(用于 json 输出)
|
|
55
|
+
* @param {Object} log - 日志对象
|
|
56
|
+
* @param {string[]} columns - 字段列表
|
|
57
|
+
* @returns {Object} 过滤后的日志对象
|
|
58
|
+
*/
|
|
59
|
+
function filterLogFields(log, columns) {
|
|
60
|
+
const result = {};
|
|
61
|
+
const keys = columns && columns.length > 0
|
|
62
|
+
? columns
|
|
63
|
+
: Object.keys(log);
|
|
64
|
+
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
if (log[key] === null || log[key] === undefined) continue;
|
|
67
|
+
result[key] = log[key];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 格式化单条日志输出
|
|
75
|
+
* @param {Object} log - 日志对象
|
|
76
|
+
* @param {Object} options - 格式化选项
|
|
77
|
+
* @param {string} options.logFormat - 输出格式(logfmt/json)
|
|
78
|
+
* @param {string} options.columns - 字段列表(逗号分隔)
|
|
79
|
+
* @param {number} options.truncate - 截断长度
|
|
80
|
+
* @param {string|string[]} options.keyword - 关键字
|
|
81
|
+
* @param {boolean} options.highlight - 是否高亮
|
|
82
|
+
*/
|
|
83
|
+
function formatLogEntry(log, options) {
|
|
84
|
+
const {
|
|
85
|
+
logFormat,
|
|
86
|
+
columns,
|
|
87
|
+
truncate,
|
|
88
|
+
keyword,
|
|
89
|
+
highlight,
|
|
90
|
+
} = options;
|
|
91
|
+
|
|
92
|
+
const isTTY = process.stdout.isTTY;
|
|
93
|
+
const RESET = ANSI_COLORS.RESET;
|
|
94
|
+
|
|
95
|
+
let columnFilter = null;
|
|
96
|
+
if (columns) {
|
|
97
|
+
columnFilter = String(columns)
|
|
98
|
+
.split(',')
|
|
99
|
+
.map((s) => s.trim())
|
|
100
|
+
.filter(Boolean);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// JSON 格式: 简单过滤后直接输出 JSON
|
|
104
|
+
if (logFormat === 'json') {
|
|
105
|
+
const filtered = filterLogFields(log, columnFilter);
|
|
106
|
+
// eslint-disable-next-line no-console
|
|
107
|
+
console.log(JSON.stringify(filtered));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// logfmt 格式
|
|
112
|
+
const fieldsToDisplay = columnFilter && columnFilter.length > 0
|
|
113
|
+
? columnFilter
|
|
114
|
+
: DISPLAY_FIELDS;
|
|
115
|
+
|
|
116
|
+
const formatted = [];
|
|
117
|
+
|
|
118
|
+
fieldsToDisplay.forEach((fieldName) => {
|
|
119
|
+
if (!Object.prototype.hasOwnProperty.call(log, fieldName)) return;
|
|
120
|
+
const rawValue = log[fieldName];
|
|
121
|
+
if (rawValue === null || rawValue === undefined) return;
|
|
122
|
+
|
|
123
|
+
const color = getFieldColor(fieldName, isTTY);
|
|
124
|
+
const value = applyHighlightAndTruncate(rawValue, fieldName, truncate, keyword, highlight);
|
|
125
|
+
|
|
126
|
+
let formattedValue = value;
|
|
127
|
+
if (typeof value === 'string'
|
|
128
|
+
&& (value.includes(' ') || value.includes('"') || value.includes('\''))) {
|
|
129
|
+
formattedValue = `"${value.replace(/"/g, '\\"')}"`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const coloredKey = `${color}${fieldName}${RESET}`;
|
|
133
|
+
formatted.push(`${coloredKey}=${formattedValue}${RESET}`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (formatted.length === 0) {
|
|
137
|
+
// 回退: 打印整个对象
|
|
138
|
+
// eslint-disable-next-line no-console
|
|
139
|
+
console.log(JSON.stringify(log));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// eslint-disable-next-line no-console
|
|
144
|
+
console.log(formatted.join(' '));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 批量输出日志数组
|
|
149
|
+
* @param {Object[]} logs - 日志数组
|
|
150
|
+
* @param {boolean} raw - 是否原始输出(暂未使用)
|
|
151
|
+
* @param {string} logFormat - 输出格式
|
|
152
|
+
* @param {string} columns - 字段列表
|
|
153
|
+
* @param {number} truncate - 截断长度
|
|
154
|
+
* @param {string|string[]} keyword - 关键字
|
|
155
|
+
* @param {boolean} highlight - 是否高亮
|
|
156
|
+
*/
|
|
157
|
+
function displayLogs(logs, raw, logFormat, columns, truncate, keyword, highlight) {
|
|
158
|
+
const opts = {
|
|
159
|
+
logFormat: logFormat || 'logfmt',
|
|
160
|
+
columns: columns || null,
|
|
161
|
+
truncate: typeof truncate === 'number' ? truncate : 2048,
|
|
162
|
+
keyword: keyword || null,
|
|
163
|
+
highlight: highlight !== false,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
logs.forEach((log) => {
|
|
167
|
+
formatLogEntry(log, opts);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
truncateValue,
|
|
173
|
+
applyHighlightAndTruncate,
|
|
174
|
+
getFieldColor,
|
|
175
|
+
filterLogFields,
|
|
176
|
+
formatLogEntry,
|
|
177
|
+
displayLogs,
|
|
178
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 关键字高亮工具
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 高亮文本中的关键字
|
|
7
|
+
* @param {string} text - 原始文本
|
|
8
|
+
* @param {string|string[]} keyword - 关键字(单个或数组)
|
|
9
|
+
* @returns {string} 高亮后的文本
|
|
10
|
+
*/
|
|
11
|
+
function highlightKeyword(text, keyword) {
|
|
12
|
+
if (!keyword || !text) return text;
|
|
13
|
+
|
|
14
|
+
const HIGHLIGHT_BG = '\x1b[43m';
|
|
15
|
+
const HIGHLIGHT_FG = '\x1b[30m';
|
|
16
|
+
const RESET = '\x1b[0m\x1b[49m';
|
|
17
|
+
|
|
18
|
+
const keywords = Array.isArray(keyword) ? keyword : [keyword];
|
|
19
|
+
let result = text;
|
|
20
|
+
|
|
21
|
+
keywords.forEach((kw) => {
|
|
22
|
+
if (kw && typeof kw === 'string') {
|
|
23
|
+
const escapedKeyword = kw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
24
|
+
const regex = new RegExp(`(${escapedKeyword})`, 'gi');
|
|
25
|
+
result = result.replace(regex, `${HIGHLIGHT_BG}${HIGHLIGHT_FG}$1${RESET}`);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
highlightKeyword,
|
|
34
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core 模块导出
|
|
3
|
+
* 通用逻辑的单一事实源
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Provider 接口
|
|
7
|
+
const { LogProvider } = require('./providers');
|
|
8
|
+
|
|
9
|
+
// 工具函数
|
|
10
|
+
const { parseTime } = require('./utils');
|
|
11
|
+
|
|
12
|
+
// 展示层
|
|
13
|
+
const { displayLogs, formatLogEntry } = require('./display/format');
|
|
14
|
+
const { highlightKeyword } = require('./display/highlight');
|
|
15
|
+
const displayConstants = require('./display/constants');
|
|
16
|
+
|
|
17
|
+
// 命令
|
|
18
|
+
const logCommand = require('./commands/log');
|
|
19
|
+
const { handleLogSearch } = require('./commands/log/search');
|
|
20
|
+
const sandboxCommand = require('./commands/sandbox');
|
|
21
|
+
const attachCommand = require('./commands/attach');
|
|
22
|
+
|
|
23
|
+
// SDK
|
|
24
|
+
const { SandboxClient, SandboxConfig, getOpenSourceSandboxConfig } = require('./sdks/sandbox');
|
|
25
|
+
|
|
26
|
+
// 配置
|
|
27
|
+
const config = require('./config');
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
// Provider
|
|
31
|
+
LogProvider,
|
|
32
|
+
|
|
33
|
+
// Utils
|
|
34
|
+
parseTime,
|
|
35
|
+
|
|
36
|
+
// Display
|
|
37
|
+
displayLogs,
|
|
38
|
+
formatLogEntry,
|
|
39
|
+
highlightKeyword,
|
|
40
|
+
displayConstants,
|
|
41
|
+
|
|
42
|
+
// Commands
|
|
43
|
+
logCommand,
|
|
44
|
+
handleLogSearch,
|
|
45
|
+
sandboxCommand,
|
|
46
|
+
attachCommand,
|
|
47
|
+
|
|
48
|
+
// SDK
|
|
49
|
+
SandboxClient,
|
|
50
|
+
SandboxConfig,
|
|
51
|
+
getOpenSourceSandboxConfig,
|
|
52
|
+
|
|
53
|
+
// Config
|
|
54
|
+
config,
|
|
55
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LogProvider 抽象接口
|
|
3
|
+
*
|
|
4
|
+
* 所有日志后端(本地文件、SLS、Loghouse 等)都应实现此接口。
|
|
5
|
+
* 这确保了命令层与具体日志存储的解耦。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} LogQueryOptions
|
|
10
|
+
* @property {number} startTime - 开始时间(毫秒时间戳)
|
|
11
|
+
* @property {number} endTime - 结束时间(毫秒时间戳)
|
|
12
|
+
* @property {string} [filter] - 过滤条件(简单关键字匹配)
|
|
13
|
+
* @property {number} [limit] - 返回条数限制
|
|
14
|
+
* @property {number} [offset] - 偏移量
|
|
15
|
+
* @property {boolean} [count] - 是否只返回数量
|
|
16
|
+
* @property {string} [table] - 表名(可选,部分后端支持)
|
|
17
|
+
* @property {string} [orderBy] - 排序字段
|
|
18
|
+
* @property {string} [orderDirection] - 排序方向(ASC/DESC)
|
|
19
|
+
* @property {string} [sandboxId] - 按 sandbox_id 过滤
|
|
20
|
+
* @property {string} [event] - 按 event 过滤
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} LogEntry
|
|
25
|
+
* @property {string} ['@timestamp'] - 时间戳(毫秒)
|
|
26
|
+
* @property {string} [timestamp] - 时间戳
|
|
27
|
+
* @property {string} [time_iso8601] - ISO8601 时间
|
|
28
|
+
* @property {string} [level] - 日志级别
|
|
29
|
+
* @property {string} [message] - 日志消息
|
|
30
|
+
* @property {string} [sandbox_id] - Sandbox ID
|
|
31
|
+
* @property {string} [trace_id] - 追踪 ID
|
|
32
|
+
* @property {string} [event] - 事件类型
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {Object} LogProviderInterface
|
|
37
|
+
* @property {function(): Promise<LogProviderInterface>} connect - 建立连接
|
|
38
|
+
* @property {function(LogQueryOptions): Promise<LogEntry[]|{total: number}[]>} query - 查询日志
|
|
39
|
+
* @property {function(): Promise<void|undefined>} close - 关闭连接
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 创建 LogProvider 的基类定义
|
|
44
|
+
* 子类应实现 connect、query、close 方法
|
|
45
|
+
*/
|
|
46
|
+
class LogProvider {
|
|
47
|
+
constructor(config = {}) {
|
|
48
|
+
this.config = config;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 建立连接
|
|
53
|
+
* @returns {Promise<LogProvider>}
|
|
54
|
+
*/
|
|
55
|
+
async connect() {
|
|
56
|
+
throw new Error('LogProvider.connect() must be implemented by subclass');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 查询日志
|
|
61
|
+
* @param {LogQueryOptions} options - 查询选项
|
|
62
|
+
* @returns {Promise<LogEntry[]|{total: number}[]>}
|
|
63
|
+
*/
|
|
64
|
+
async query(options) {
|
|
65
|
+
throw new Error('LogProvider.query() must be implemented by subclass');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 关闭连接
|
|
70
|
+
* @returns {Promise<void|undefined>}
|
|
71
|
+
*/
|
|
72
|
+
async close() {
|
|
73
|
+
throw new Error('LogProvider.close() must be implemented by subclass');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
LogProvider,
|
|
79
|
+
};
|