@lark-apaas/miaoda-cli 0.1.4 → 0.1.5

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/README.md CHANGED
@@ -36,10 +36,11 @@ miaoda file ls --output json
36
36
 
37
37
  命令以"域"作为第一级命名空间:
38
38
 
39
- | 域 | 用途 |
40
- |---|---|
41
- | `miaoda file ...` | 文件操作:上传、下载、元数据、签名下载、批量删除 |
42
- | `miaoda db ...` | 数据操作:SQL 执行、表结构查询、数据导入导出 |
39
+ | 域 | 用途 |
40
+ | -------------------------- | ---------------------------------------------------- |
41
+ | `miaoda file ...` | 文件操作:上传、下载、元数据、签名下载、批量删除 |
42
+ | `miaoda db ...` | 数据操作:SQL 执行、表结构查询、数据导入导出 |
43
+ | `miaoda observability ...` | 线上日志、链路、前端源码堆栈反查、监控指标、运营指标 |
43
44
 
44
45
  完整命令通过 `miaoda --help` 或 `miaoda <domain> --help` 查看。
45
46
 
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.searchLogs = searchLogs;
4
4
  exports.searchTraces = searchTraces;
5
5
  exports.getTraces = getTraces;
6
+ exports.resolveStackTrace = resolveStackTrace;
6
7
  exports.queryMetricsData = queryMetricsData;
7
8
  exports.queryAnalyticsData = queryAnalyticsData;
8
9
  const http_1 = require("../../utils/http");
@@ -30,6 +31,15 @@ async function getTraces(req) {
30
31
  defaultErrCode: DEFAULT_ERR_CODE,
31
32
  });
32
33
  }
34
+ // ── 前端源码堆栈反解 ──
35
+ async function resolveStackTrace(req) {
36
+ const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/resolve_stack_trace`;
37
+ const { appID: _appID, ...body } = req;
38
+ return (0, http_1.postInnerApi)(url, body, {
39
+ errPrefix: 'Failed to resolve frontend source stack',
40
+ defaultErrCode: DEFAULT_ERR_CODE,
41
+ });
42
+ }
33
43
  // ── 监控指标 ──
34
44
  async function queryMetricsData(req) {
35
45
  const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/metrics/data/query`;
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StatusCode = exports.SpanKind = exports.WordOperator = exports.spanSchema = exports.logItemSchema = exports.queryAnalyticsData = exports.queryMetricsData = exports.getTraces = exports.searchTraces = exports.searchLogs = void 0;
3
+ exports.StatusCode = exports.SpanKind = exports.WordOperator = exports.spanSchema = exports.logItemSchema = exports.queryAnalyticsData = exports.queryMetricsData = exports.resolveStackTrace = exports.getTraces = exports.searchTraces = exports.searchLogs = void 0;
4
4
  var api_1 = require("./api");
5
5
  Object.defineProperty(exports, "searchLogs", { enumerable: true, get: function () { return api_1.searchLogs; } });
6
6
  Object.defineProperty(exports, "searchTraces", { enumerable: true, get: function () { return api_1.searchTraces; } });
7
7
  Object.defineProperty(exports, "getTraces", { enumerable: true, get: function () { return api_1.getTraces; } });
8
+ Object.defineProperty(exports, "resolveStackTrace", { enumerable: true, get: function () { return api_1.resolveStackTrace; } });
8
9
  Object.defineProperty(exports, "queryMetricsData", { enumerable: true, get: function () { return api_1.queryMetricsData; } });
9
10
  Object.defineProperty(exports, "queryAnalyticsData", { enumerable: true, get: function () { return api_1.queryAnalyticsData; } });
10
11
  var schemas_1 = require("./schemas");
@@ -14,6 +14,24 @@ const COMMON_TIME_HELP = `
14
14
  1) 不带时区的形式与 pretty 输出闭环(同机器复制粘贴稳定);跨机器请带显式时区。
15
15
  2) 必须用 T 分隔日期与时间,禁止空格——shell 会把不带引号的 'YYYY-MM-DD HH:mm:ss' 拆成两个参数。
16
16
  `;
17
+ function normalizeRepeatedOption(value) {
18
+ if (Array.isArray(value)) {
19
+ const items = value.filter((item) => typeof item === 'string' && item.length > 0);
20
+ return items.length === 1 ? items[0] : items;
21
+ }
22
+ return value;
23
+ }
24
+ function normalizeRepeatedArray(value) {
25
+ if (Array.isArray(value)) {
26
+ return value.filter((item) => typeof item === 'string' && item.length > 0);
27
+ }
28
+ return typeof value === 'string' && value ? [value] : [];
29
+ }
30
+ function compactRepeatedOption(values) {
31
+ if (values.length === 0)
32
+ return undefined;
33
+ return values.length === 1 ? values[0] : values;
34
+ }
17
35
  function registerObservabilityCommands(program) {
18
36
  const obCmd = program
19
37
  .command('observability')
@@ -31,6 +49,7 @@ function registerObservabilityCommands(program) {
31
49
  `);
32
50
  registerLog(obCmd);
33
51
  registerTrace(obCmd);
52
+ registerSourceStack(obCmd);
34
53
  registerMetric(obCmd);
35
54
  registerAnalytics(obCmd);
36
55
  }
@@ -45,8 +64,8 @@ function registerLog(parent) {
45
64
  .argParser((0, shared_1.caseInsensitiveChoice)(['DEBUG', 'INFO', 'WARN', 'ERROR'])))
46
65
  .option('--since <time>', '开始时间')
47
66
  .option('--until <time>', '截止时间')
48
- .option('--log-id <id>', '按请求 logid 过滤(attributes.ob_data_id)')
49
- .option('--trace-id <id>', '按 trace ID 过滤')
67
+ .addOption(new commander_1.Option('--log-id <id>', '按请求 logid 过滤(可重复传入)').argParser(shared_1.collectRepeatedOption))
68
+ .addOption(new commander_1.Option('--trace-id <id>', '按 trace ID 过滤(可重复传入)').argParser(shared_1.collectRepeatedOption))
50
69
  .option('--grep <pattern>', '按关键字模糊搜索 body')
51
70
  .option('--module <name>', '按模块名过滤')
52
71
  .option('--user-id <user-id>', '按触发用户 ID 过滤')
@@ -63,7 +82,9 @@ JSON 输出
63
82
  示例
64
83
  $ miaoda observability log --since 1h --level error --json
65
84
  $ miaoda observability log --log-id log_abc123 --json
85
+ $ miaoda observability log --log-id log_1 --log-id log_2 --json
66
86
  $ miaoda observability log --trace-id 140ebb5ac9b... --json
87
+ $ miaoda observability log --trace-id trace_1 --trace-id trace_2 --json
67
88
  $ miaoda observability log --api /api/orders --min-duration 200 --json
68
89
  `);
69
90
  cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
@@ -74,8 +95,8 @@ JSON 输出
74
95
  level: rawOpts.level,
75
96
  since: rawOpts.since,
76
97
  until: rawOpts.until,
77
- logId: rawOpts.logId,
78
- traceId: rawOpts.traceId,
98
+ logId: normalizeRepeatedOption(rawOpts.logId),
99
+ traceId: normalizeRepeatedOption(rawOpts.traceId),
79
100
  grep: rawOpts.grep,
80
101
  module: rawOpts.module,
81
102
  userId: rawOpts.userId,
@@ -100,7 +121,7 @@ function registerTrace(parent) {
100
121
  .addOption((0, shared_1.appIdOption)().hideHelp())
101
122
  .option('--since <time>', '开始时间')
102
123
  .option('--until <time>', '截止时间')
103
- .option('--trace-id <id>', '按 trace ID 过滤')
124
+ .addOption(new commander_1.Option('--trace-id <id>', '按 trace ID 过滤(可重复传入)').argParser(shared_1.collectRepeatedOption))
104
125
  .option('--root-span <span-name>', '按入口节点关键词过滤')
105
126
  .option('--user-id <user-id>', '按触发用户 ID 过滤')
106
127
  .option('--limit <n>', '返回条数上限(1~100)', parseLimit, 50)
@@ -111,6 +132,7 @@ JSON 输出
111
132
 
112
133
  示例
113
134
  $ miaoda observability trace list --since 1h --json
135
+ $ miaoda observability trace list --trace-id trace_1 --trace-id trace_2 --json
114
136
  $ miaoda observability trace list --root-span api-gateway --limit 20 --json
115
137
  `);
116
138
  listCmd.action((0, shared_1.withHelp)(listCmd, async (rawOpts) => {
@@ -120,7 +142,7 @@ JSON 输出
120
142
  appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
121
143
  since: rawOpts.since,
122
144
  until: rawOpts.until,
123
- traceId: rawOpts.traceId,
145
+ traceId: normalizeRepeatedOption(rawOpts.traceId),
124
146
  rootSpan: rawOpts.rootSpan,
125
147
  userId: rawOpts.userId,
126
148
  limit: rawOpts.limit,
@@ -154,6 +176,40 @@ JSON 输出
154
176
  });
155
177
  }));
156
178
  }
179
+ // ── source-stack ──
180
+ function registerSourceStack(parent) {
181
+ const cmd = parent
182
+ .command('source-stack')
183
+ .description('反解前端错误堆栈到源码位置')
184
+ .argument('[log-ids...]', '前端错误请求 logid,可传多个')
185
+ .addOption((0, shared_1.appIdOption)().hideHelp())
186
+ .addOption(new commander_1.Option('--log-id <id>', '前端错误请求 logid(可重复传入)').argParser(shared_1.collectRepeatedOption))
187
+ .addOption(new commander_1.Option('--trace-id <id>', '包含前端错误的 trace ID(可重复传入)').argParser(shared_1.collectRepeatedOption))
188
+ .option('--since <time>', '开始时间')
189
+ .option('--until <time>', '截止时间')
190
+ .option('--limit <n>', '每类查询返回条数上限(1~100)', parseLimit, 50)
191
+ .addHelpText('after', `${COMMON_TIME_HELP}
192
+ JSON 输出
193
+ {"data": [{"log_id": "...", "trace_id": "...", "status": "resolved", "error_message_stack": "Error: boom\\n at src/App.tsx:42:7"}], "next_cursor": null, "has_more": false}
194
+
195
+ 示例
196
+ $ miaoda observability source-stack --log-id log_1 --log-id log_2 --json
197
+ $ miaoda observability source-stack --trace-id trace_1 --trace-id trace_2 --json
198
+ $ miaoda observability source-stack log_abc123 --json
199
+ `);
200
+ cmd.action((0, shared_1.withHelp)(cmd, async (logIds, rawOpts) => {
201
+ (0, shared_1.validateTimeOptions)(rawOpts, 'since', 'until');
202
+ (0, shared_1.rejectCliOverride)(cmd, 'appId');
203
+ await (0, index_1.handleObservabilitySourceStack)({
204
+ appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
205
+ logId: compactRepeatedOption([...logIds, ...normalizeRepeatedArray(rawOpts.logId)]),
206
+ traceId: normalizeRepeatedOption(rawOpts.traceId),
207
+ since: rawOpts.since,
208
+ until: rawOpts.until,
209
+ limit: rawOpts.limit,
210
+ });
211
+ }));
212
+ }
157
213
  // ── metric ──
158
214
  function registerMetric(parent) {
159
215
  const cmd = parent
@@ -7,6 +7,7 @@ exports.softRequiredOption = softRequiredOption;
7
7
  exports.resolveAppId = resolveAppId;
8
8
  exports.withHelp = withHelp;
9
9
  exports.caseInsensitiveChoice = caseInsensitiveChoice;
10
+ exports.collectRepeatedOption = collectRepeatedOption;
10
11
  exports.validateTimeOptions = validateTimeOptions;
11
12
  exports.rejectCliOverride = rejectCliOverride;
12
13
  const commander_1 = require("commander");
@@ -86,6 +87,10 @@ function caseInsensitiveChoice(canonical) {
86
87
  return hit;
87
88
  };
88
89
  }
90
+ /** Commander repeatable option parser:重复传入时收集为数组。 */
91
+ function collectRepeatedOption(value, previous = []) {
92
+ return [...previous, value];
93
+ }
89
94
  /**
90
95
  * --since / --until 等时间参数的 action 阶段校验。
91
96
  *
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ceilMsToBucket = exports.floorMsToBucket = exports.msToSec = exports.msToNs = exports.parseToSec = exports.parseToNs = exports.parseToMs = exports.parseTimeToMs = void 0;
4
4
  exports.eqFilter = eqFilter;
5
+ exports.inFilter = inFilter;
5
6
  exports.rangeFilter = rangeFilter;
6
7
  exports.fuzzyFilter = fuzzyFilter;
7
8
  exports.buildFieldFilters = buildFieldFilters;
@@ -27,6 +28,9 @@ function eqFilter(value, type = 'str') {
27
28
  const v = type !== 'str' ? Number(value) : value;
28
29
  return { [type]: { eq: v } };
29
30
  }
31
+ function inFilter(strs) {
32
+ return { str: { in: strs } };
33
+ }
30
34
  /** i64 数值范围过滤 → { i64: { gte, lte } };输入接受 string(CLI flag)或 number */
31
35
  function rangeFilter(opts) {
32
36
  const i64 = {};
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleObservabilityAnalytics = exports.handleObservabilityMetric = exports.handleObservabilityTraceGet = exports.handleObservabilityTraceList = exports.handleObservabilityLog = void 0;
3
+ exports.handleObservabilityAnalytics = exports.handleObservabilityMetric = exports.handleObservabilitySourceStack = exports.handleObservabilityTraceGet = exports.handleObservabilityTraceList = exports.handleObservabilityLog = void 0;
4
4
  var log_1 = require("./log");
5
5
  Object.defineProperty(exports, "handleObservabilityLog", { enumerable: true, get: function () { return log_1.handleObservabilityLog; } });
6
6
  var trace_1 = require("./trace");
7
7
  Object.defineProperty(exports, "handleObservabilityTraceList", { enumerable: true, get: function () { return trace_1.handleObservabilityTraceList; } });
8
8
  Object.defineProperty(exports, "handleObservabilityTraceGet", { enumerable: true, get: function () { return trace_1.handleObservabilityTraceGet; } });
9
+ var source_stack_1 = require("./source-stack");
10
+ Object.defineProperty(exports, "handleObservabilitySourceStack", { enumerable: true, get: function () { return source_stack_1.handleObservabilitySourceStack; } });
9
11
  var metric_1 = require("./metric");
10
12
  Object.defineProperty(exports, "handleObservabilityMetric", { enumerable: true, get: function () { return metric_1.handleObservabilityMetric; } });
11
13
  var analytics_1 = require("./analytics");
@@ -38,6 +38,12 @@ const api = __importStar(require("../../../api/index"));
38
38
  const output_1 = require("../../../utils/output");
39
39
  const index_1 = require("../../../api/observability/index");
40
40
  const helpers_1 = require("./helpers");
41
+ function stringIdFilter(value) {
42
+ if (Array.isArray(value)) {
43
+ return value.length > 0 ? (0, helpers_1.inFilter)(value) : undefined;
44
+ }
45
+ return value ? (0, helpers_1.eqFilter)(value) : undefined;
46
+ }
41
47
  /** miaoda observability log */
42
48
  async function handleObservabilityLog(opts) {
43
49
  const appID = opts.appId;
@@ -50,8 +56,8 @@ async function handleObservabilityLog(opts) {
50
56
  // - duration_ms 暂未在 BAM 描述里明确列出,留 TODO 等 e2e 验证
51
57
  const fieldFilters = (0, helpers_1.buildFieldFilters)([
52
58
  { key: 'severity_text', value: opts.level ? (0, helpers_1.eqFilter)(opts.level) : undefined },
53
- { key: 'attributes.ob_data_id', value: opts.logId ? (0, helpers_1.eqFilter)(opts.logId) : undefined },
54
- { key: 'trace_id', value: opts.traceId ? (0, helpers_1.eqFilter)(opts.traceId) : undefined },
59
+ { key: 'attributes.ob_data_id', value: stringIdFilter(opts.logId) },
60
+ { key: 'trace_id', value: stringIdFilter(opts.traceId) },
55
61
  { key: 'module', value: opts.module ? (0, helpers_1.eqFilter)(opts.module) : undefined },
56
62
  { key: 'user_id', value: opts.userId ? (0, helpers_1.eqFilter)(opts.userId, 'i64') : undefined },
57
63
  { key: 'page', value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
@@ -0,0 +1,389 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleObservabilitySourceStack = handleObservabilitySourceStack;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const output_1 = require("../../../utils/output");
39
+ const args_1 = require("../../../utils/args");
40
+ const helpers_1 = require("./helpers");
41
+ const SOURCE_MAP_FILE_PREFIX = 'client/assets/';
42
+ /** miaoda observability source-stack */
43
+ async function handleObservabilitySourceStack(opts) {
44
+ const logIds = normalizeValues(opts.logId);
45
+ const traceIds = normalizeValues(opts.traceId);
46
+ if (logIds.length === 0 && traceIds.length === 0) {
47
+ (0, args_1.failArgs)('至少传入线上环境日志 ID 或 trace ID');
48
+ }
49
+ const logs = await collectCandidateLogs(opts, { logIds, traceIds });
50
+ const results = await resolveCandidateLogs(opts.appId, logs);
51
+ (0, output_1.emit)({ data: results, next_cursor: null, has_more: false });
52
+ }
53
+ async function collectCandidateLogs(opts, params) {
54
+ const limit = (0, helpers_1.validateLimit)(opts.limit ?? 50);
55
+ const searches = [];
56
+ if (params.logIds.length > 0) {
57
+ searches.push(searchLogs(opts, {
58
+ limit,
59
+ fieldFilters: (0, helpers_1.buildFieldFilters)([
60
+ { key: 'attributes.ob_data_id', value: stringIdFilter(params.logIds) },
61
+ ]),
62
+ }));
63
+ }
64
+ if (params.traceIds.length > 0) {
65
+ searches.push(searchLogs(opts, {
66
+ limit,
67
+ fieldFilters: (0, helpers_1.buildFieldFilters)([
68
+ { key: 'trace_id', value: stringIdFilter(params.traceIds) },
69
+ ]),
70
+ }));
71
+ }
72
+ const allLogs = (await Promise.all(searches)).flat();
73
+ const deduped = new Map();
74
+ for (const log of allLogs) {
75
+ deduped.set(log.id, log);
76
+ }
77
+ return Array.from(deduped.values());
78
+ }
79
+ async function searchLogs(opts, params) {
80
+ const req = {
81
+ appID: opts.appId,
82
+ appEnv: 'runtime',
83
+ startTimestampNs: (0, helpers_1.parseToNs)(opts.since),
84
+ endTimestampNs: (0, helpers_1.parseToNs)(opts.until),
85
+ limit: params.limit,
86
+ fieldFilters: params.fieldFilters,
87
+ };
88
+ const resp = await api.observability.searchLogs(req);
89
+ return resp.logItems ?? [];
90
+ }
91
+ async function resolveCandidateLogs(appID, logs) {
92
+ const results = [];
93
+ for (const log of logs) {
94
+ const attrs = log.attributes ?? {};
95
+ const rawStack = pickAttr(attrs, ['stack', 'error_stack', 'errorStack']) ??
96
+ pickBodyStack(log.body) ??
97
+ log.body;
98
+ const frames = parseGeneratedFrames(rawStack);
99
+ const commitID = pickAttr(attrs, [
100
+ 'release_commit_id',
101
+ 'releaseCommitID',
102
+ 'commit_id',
103
+ 'commitID',
104
+ ]);
105
+ const message = pickErrorMessage(log, rawStack);
106
+ const base = {
107
+ log_id: log.id,
108
+ trace_id: log.traceID ?? attrs.trace_id,
109
+ commit_id: commitID,
110
+ source_map_files: sourceMapFilesForFrames(frames),
111
+ };
112
+ const reason = missingReason({ frames, commitID });
113
+ if (reason) {
114
+ results.push({
115
+ ...base,
116
+ status: 'unresolved',
117
+ reason,
118
+ error_message_stack: formatStack(message, frames),
119
+ });
120
+ continue;
121
+ }
122
+ const resolvedCommitID = commitID;
123
+ if (!resolvedCommitID)
124
+ continue;
125
+ let resolvedFrames;
126
+ try {
127
+ resolvedFrames = await resolveFramesBySourceMap(appID, resolvedCommitID, frames);
128
+ }
129
+ catch (err) {
130
+ results.push({
131
+ ...base,
132
+ status: 'unresolved',
133
+ reason: `调用 resolve_stack_trace 失败:${errorMessage(err)}`,
134
+ error_message_stack: formatStack(message, frames),
135
+ });
136
+ continue;
137
+ }
138
+ results.push({
139
+ ...base,
140
+ status: 'resolved',
141
+ error_message_stack: formatStack(message, resolvedFrames),
142
+ });
143
+ }
144
+ return results;
145
+ }
146
+ function normalizeValues(value) {
147
+ if (Array.isArray(value)) {
148
+ return value.filter((item) => item.length > 0);
149
+ }
150
+ return value ? [value] : [];
151
+ }
152
+ function stringIdFilter(values) {
153
+ if (values.length === 0)
154
+ return undefined;
155
+ return values.length === 1 ? (0, helpers_1.eqFilter)(values[0]) : (0, helpers_1.inFilter)(values);
156
+ }
157
+ function pickAttr(attrs, keys) {
158
+ for (const key of keys) {
159
+ const value = attrs[key];
160
+ if (value)
161
+ return value;
162
+ }
163
+ return undefined;
164
+ }
165
+ async function resolveFramesBySourceMap(appID, commitID, frames) {
166
+ const resp = await api.observability.resolveStackTrace({
167
+ appID,
168
+ commitID,
169
+ sourceMapFilePrefix: SOURCE_MAP_FILE_PREFIX,
170
+ frames,
171
+ });
172
+ return resp.frames ?? frames;
173
+ }
174
+ function parseGeneratedFrames(stack) {
175
+ if (!stack)
176
+ return [];
177
+ const jsonFrames = parseJsonStackFrames(stack);
178
+ if (jsonFrames.length > 0)
179
+ return jsonFrames;
180
+ const frames = [];
181
+ const parenFramePattern = /\((.+):(\d+):(\d+)\)$/;
182
+ const plainFramePattern = /(?:at\s+)?(.+):(\d+):(\d+)$/;
183
+ for (const line of stack.split('\n')) {
184
+ const trimmed = line.trim();
185
+ const match = parenFramePattern.exec(trimmed) ?? plainFramePattern.exec(trimmed);
186
+ if (!match)
187
+ continue;
188
+ const [, fileName, lineNumber, columnNumber] = match;
189
+ if (!fileName || !lineNumber || !columnNumber)
190
+ continue;
191
+ frames.push({
192
+ fileName: normalizeFrameFileName(fileName),
193
+ line: Number(lineNumber),
194
+ column: Number(columnNumber),
195
+ });
196
+ }
197
+ return frames.slice(0, 2000);
198
+ }
199
+ function parseJsonStackFrames(stack) {
200
+ const trimmed = stack.trim();
201
+ if (!trimmed.startsWith('['))
202
+ return [];
203
+ let parsed;
204
+ try {
205
+ parsed = JSON.parse(trimmed);
206
+ }
207
+ catch {
208
+ return [];
209
+ }
210
+ if (!Array.isArray(parsed))
211
+ return [];
212
+ return parsed
213
+ .map((item) => normalizeJsonFrame(item))
214
+ .filter((frame) => Boolean(frame))
215
+ .slice(0, 2000);
216
+ }
217
+ function normalizeJsonFrame(item) {
218
+ if (!isRecord(item))
219
+ return undefined;
220
+ const fileName = item.fileName;
221
+ const line = item.line;
222
+ const column = item.column;
223
+ if (typeof fileName !== 'string')
224
+ return undefined;
225
+ const lineNumber = toFiniteNumber(line);
226
+ const columnNumber = toFiniteNumber(column);
227
+ if (lineNumber === undefined || columnNumber === undefined)
228
+ return undefined;
229
+ return {
230
+ fileName: normalizeFrameFileName(fileName),
231
+ line: lineNumber,
232
+ column: columnNumber,
233
+ };
234
+ }
235
+ function pickErrorMessage(log, rawStack) {
236
+ return (pickBodyErrorMessage(log.body) ??
237
+ firstTextStackLine(rawStack) ??
238
+ pickAttr(log.attributes ?? {}, ['error_message', 'errorMessage', 'message']));
239
+ }
240
+ function pickBodyStack(body) {
241
+ const parsed = parseBodyRecord(body);
242
+ if (!parsed)
243
+ return undefined;
244
+ return (stringProp(parsed, 'stack') ??
245
+ nestedStringProp(parsed, ['error', 'stack']) ??
246
+ nestedStringProp(parsed, ['response', 'error', 'stack']) ??
247
+ pickConsoleArgsStack(parsed));
248
+ }
249
+ function pickBodyErrorMessage(body) {
250
+ const parsed = parseBodyRecord(body);
251
+ if (!parsed)
252
+ return undefined;
253
+ return (stringProp(parsed, 'error_message') ??
254
+ stringProp(parsed, 'message') ??
255
+ nestedStringProp(parsed, ['error', 'message']) ??
256
+ nestedStringProp(parsed, ['response', 'error', 'message']) ??
257
+ pickConsoleArgsMessage(parsed));
258
+ }
259
+ function pickConsoleArgsMessage(record) {
260
+ const parts = Object.entries(record)
261
+ .filter(([key]) => /^\d+$/.test(key))
262
+ .sort(([left], [right]) => Number(left) - Number(right))
263
+ .map(([, value]) => consoleArgMessage(value))
264
+ .filter((message) => Boolean(message));
265
+ if (parts.length === 0)
266
+ return undefined;
267
+ return parts.join(' ').replace(/\s+/g, ' ').trim();
268
+ }
269
+ function pickConsoleArgsStack(record) {
270
+ return Object.entries(record)
271
+ .filter(([key]) => /^\d+$/.test(key))
272
+ .sort(([left], [right]) => Number(left) - Number(right))
273
+ .map(([, value]) => consoleArgStack(value))
274
+ .find((stack) => Boolean(stack));
275
+ }
276
+ function consoleArgStack(value) {
277
+ if (typeof value === 'string') {
278
+ const stack = value.trim();
279
+ return looksLikeTextStack(stack) ? stack : undefined;
280
+ }
281
+ if (!isRecord(value))
282
+ return undefined;
283
+ return (stringProp(value, 'stack') ??
284
+ nestedStringProp(value, ['error', 'stack']) ??
285
+ nestedStringProp(value, ['response', 'error', 'stack']));
286
+ }
287
+ function consoleArgMessage(value) {
288
+ if (typeof value === 'string') {
289
+ const message = value.trim();
290
+ return message.length > 0 ? message : undefined;
291
+ }
292
+ if (!isRecord(value))
293
+ return undefined;
294
+ return errorRecordMessage(value);
295
+ }
296
+ function errorRecordMessage(record) {
297
+ const stackTitle = firstTextStackLine(stringProp(record, 'stack'));
298
+ if (stackTitle)
299
+ return stackTitle;
300
+ const name = stringProp(record, 'name');
301
+ const message = stringProp(record, 'message') ??
302
+ nestedStringProp(record, ['error', 'message']) ??
303
+ nestedStringProp(record, ['response', 'error', 'message']);
304
+ if (name && message && !message.startsWith(`${name}:`)) {
305
+ return `${name}: ${message}`;
306
+ }
307
+ return message ?? name;
308
+ }
309
+ function parseBodyRecord(body) {
310
+ if (!body)
311
+ return undefined;
312
+ let parsed;
313
+ try {
314
+ parsed = JSON.parse(body);
315
+ }
316
+ catch {
317
+ return undefined;
318
+ }
319
+ return isRecord(parsed) ? parsed : undefined;
320
+ }
321
+ function firstTextStackLine(stack) {
322
+ const trimmed = stack?.trim();
323
+ if (!trimmed || trimmed.startsWith('['))
324
+ return undefined;
325
+ return trimmed
326
+ .split('\n')
327
+ .map((line) => line.trim())
328
+ .find((line) => line.length > 0 && !line.startsWith('at '));
329
+ }
330
+ function looksLikeTextStack(value) {
331
+ return value.length > 0 && /:\d+:\d+(?:\)|$)/.test(value);
332
+ }
333
+ function missingReason(params) {
334
+ if (!params.commitID)
335
+ return '日志缺少 release_commit_id / commit_id,无法定位构建产物';
336
+ if (params.frames.length === 0)
337
+ return '日志中没有可解析的 generated stack frame';
338
+ return undefined;
339
+ }
340
+ function normalizeFrameFileName(fileName) {
341
+ return fileName.split(/[/?#]/).filter(Boolean).pop() ?? fileName;
342
+ }
343
+ function formatStack(message, frames) {
344
+ const stack = frames
345
+ .map((frame) => {
346
+ if (frame.sourceFileName) {
347
+ return ` at ${frame.sourceFileName}:${String(frame.sourceLine)}:${String(frame.sourceColumn)}`;
348
+ }
349
+ return ` at ${frame.fileName}:${String(frame.line)}:${String(frame.column)}`;
350
+ })
351
+ .join('\n');
352
+ if (message && stack)
353
+ return `${message}\n${stack}`;
354
+ return message ?? stack;
355
+ }
356
+ function isRecord(value) {
357
+ return typeof value === 'object' && value !== null;
358
+ }
359
+ function toFiniteNumber(value) {
360
+ if (typeof value === 'number' && Number.isFinite(value))
361
+ return value;
362
+ if (typeof value === 'string' && value.trim()) {
363
+ const numberValue = Number(value);
364
+ return Number.isFinite(numberValue) ? numberValue : undefined;
365
+ }
366
+ return undefined;
367
+ }
368
+ function stringProp(record, key) {
369
+ const value = record[key];
370
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
371
+ }
372
+ function nestedStringProp(record, path) {
373
+ let current = record;
374
+ for (const key of path) {
375
+ if (!isRecord(current))
376
+ return undefined;
377
+ current = current[key];
378
+ }
379
+ return typeof current === 'string' && current.length > 0 ? current : undefined;
380
+ }
381
+ function errorMessage(err) {
382
+ return err instanceof Error ? err.message : String(err);
383
+ }
384
+ function sourceMapFileForFrame(fileName) {
385
+ return `${SOURCE_MAP_FILE_PREFIX}${fileName}.map`;
386
+ }
387
+ function sourceMapFilesForFrames(frames) {
388
+ return Array.from(new Set(frames.map((frame) => sourceMapFileForFrame(frame.fileName))));
389
+ }
@@ -40,6 +40,12 @@ const output_1 = require("../../../utils/output");
40
40
  const args_1 = require("../../../utils/args");
41
41
  const index_1 = require("../../../api/observability/index");
42
42
  const helpers_1 = require("./helpers");
43
+ function stringIdFilter(value) {
44
+ if (Array.isArray(value)) {
45
+ return value.length > 0 ? (0, helpers_1.inFilter)(value) : undefined;
46
+ }
47
+ return value ? (0, helpers_1.eqFilter)(value) : undefined;
48
+ }
43
49
  /** miaoda observability trace list */
44
50
  async function handleObservabilityTraceList(opts) {
45
51
  const appID = opts.appId;
@@ -51,7 +57,7 @@ async function handleObservabilityTraceList(opts) {
51
57
  // - 入口 span:BAM 没有 entrySpanName fieldFilter,统一走 fuzzyFilter(trace endpoint
52
58
  // 默认作用于 span name),所以 --root-span 不进 fieldFilters
53
59
  const fieldFilters = (0, helpers_1.buildFieldFilters)([
54
- { key: 'trace_id', value: opts.traceId ? (0, helpers_1.eqFilter)(opts.traceId) : undefined },
60
+ { key: 'trace_id', value: stringIdFilter(opts.traceId) },
55
61
  { key: 'user_id', value: opts.userId ? (0, helpers_1.eqFilter)(opts.userId, 'i64') : undefined },
56
62
  ]);
57
63
  const req = {
package/dist/utils/git.js CHANGED
@@ -2,6 +2,27 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCurrentGitBranch = getCurrentGitBranch;
4
4
  const node_child_process_1 = require("node:child_process");
5
+ const GIT_LOCAL_ENV_KEYS = new Set([
6
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
7
+ 'GIT_CONFIG',
8
+ 'GIT_CONFIG_PARAMETERS',
9
+ 'GIT_CONFIG_COUNT',
10
+ 'GIT_OBJECT_DIRECTORY',
11
+ 'GIT_DIR',
12
+ 'GIT_WORK_TREE',
13
+ 'GIT_IMPLICIT_WORK_TREE',
14
+ 'GIT_GRAFT_FILE',
15
+ 'GIT_INDEX_FILE',
16
+ 'GIT_NO_REPLACE_OBJECTS',
17
+ 'GIT_REPLACE_REF_BASE',
18
+ 'GIT_PREFIX',
19
+ 'GIT_INTERNAL_SUPER_PREFIX',
20
+ 'GIT_SHALLOW_FILE',
21
+ 'GIT_COMMON_DIR',
22
+ ]);
23
+ function withoutGitLocalEnv() {
24
+ return Object.fromEntries(Object.entries(process.env).filter(([key]) => !GIT_LOCAL_ENV_KEYS.has(key)));
25
+ }
5
26
  /**
6
27
  * 读取 cwd 所在仓库的当前分支名。
7
28
  *
@@ -13,6 +34,7 @@ function getCurrentGitBranch(cwd = process.cwd()) {
13
34
  try {
14
35
  result = (0, node_child_process_1.spawnSync)('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
15
36
  cwd,
37
+ env: withoutGitLocalEnv(),
16
38
  encoding: 'utf8',
17
39
  stdio: ['ignore', 'pipe', 'ignore'],
18
40
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {