@lark-apaas/miaoda-cli 0.1.2 → 0.1.3-alpha.4bf312e
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 +8 -7
- package/dist/api/app/api.js +25 -0
- package/dist/api/app/index.js +15 -0
- package/dist/api/app/schemas.js +79 -0
- package/dist/api/app/types.js +58 -0
- package/dist/api/db/api.js +317 -6
- package/dist/api/db/client.js +36 -0
- package/dist/api/db/index.js +11 -1
- package/dist/api/deploy/api.js +60 -0
- package/dist/api/deploy/index.js +16 -0
- package/dist/api/deploy/schemas.js +105 -0
- package/dist/api/deploy/types.js +22 -0
- package/dist/api/file/api.js +15 -0
- package/dist/api/file/index.js +2 -1
- package/dist/api/index.js +7 -1
- package/dist/api/observability/api.js +52 -0
- package/dist/api/observability/index.js +16 -0
- package/dist/api/observability/schemas.js +60 -0
- package/dist/api/observability/types.js +27 -0
- package/dist/cli/commands/app/index.js +62 -0
- package/dist/cli/commands/db/index.js +440 -5
- package/dist/cli/commands/deploy/index.js +155 -0
- package/dist/cli/commands/file/index.js +13 -5
- package/dist/cli/commands/index.js +10 -6
- package/dist/cli/commands/observability/index.js +240 -0
- package/dist/cli/commands/shared.js +83 -7
- package/dist/cli/handlers/app/get.js +47 -0
- package/dist/cli/handlers/app/index.js +7 -0
- package/dist/cli/handlers/app/update.js +59 -0
- package/dist/cli/handlers/db/audit.js +285 -0
- package/dist/cli/handlers/db/changelog.js +117 -0
- package/dist/cli/handlers/db/data.js +3 -4
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +235 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +328 -0
- package/dist/cli/handlers/db/schema.js +2 -3
- package/dist/cli/handlers/db/sql.js +1 -2
- package/dist/cli/handlers/deploy/deploy.js +84 -0
- package/dist/cli/handlers/deploy/error-log.js +60 -0
- package/dist/cli/handlers/deploy/format.js +39 -0
- package/dist/cli/handlers/deploy/get.js +71 -0
- package/dist/cli/handlers/deploy/helpers.js +41 -0
- package/dist/cli/handlers/deploy/history.js +70 -0
- package/dist/cli/handlers/deploy/index.js +14 -0
- package/dist/cli/handlers/deploy/polling.js +162 -0
- package/dist/cli/handlers/file/cp.js +1 -2
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +1 -2
- package/dist/cli/handlers/file/quota.js +66 -0
- package/dist/cli/handlers/file/rm.js +1 -2
- package/dist/cli/handlers/file/sign.js +1 -2
- package/dist/cli/handlers/file/stat.js +1 -2
- package/dist/cli/handlers/observability/analytics.js +212 -0
- package/dist/cli/handlers/observability/helpers.js +66 -0
- package/dist/cli/handlers/observability/index.js +12 -0
- package/dist/cli/handlers/observability/log.js +94 -0
- package/dist/cli/handlers/observability/metric.js +208 -0
- package/dist/cli/handlers/observability/trace.js +102 -0
- package/dist/main.js +6 -2
- package/dist/utils/args.js +8 -0
- package/dist/utils/devops-error.js +28 -0
- package/dist/utils/git.js +29 -0
- package/dist/utils/http.js +118 -0
- package/dist/utils/index.js +15 -1
- package/dist/utils/output.js +360 -7
- package/dist/utils/poll.js +27 -0
- package/dist/utils/time.js +203 -0
- package/package.json +7 -5
|
@@ -40,7 +40,6 @@ exports.handleFileRm = handleFileRm;
|
|
|
40
40
|
const api = __importStar(require("../../../api/index"));
|
|
41
41
|
const output_1 = require("../../../utils/output");
|
|
42
42
|
const error_1 = require("../../../utils/error");
|
|
43
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
44
43
|
const index_1 = require("../../../api/file/index");
|
|
45
44
|
const render_1 = require("../../../utils/render");
|
|
46
45
|
const colors_1 = require("../../../utils/colors");
|
|
@@ -133,7 +132,7 @@ async function handleFileRm(paths, opts) {
|
|
|
133
132
|
if (totalCount > MAX_BATCH) {
|
|
134
133
|
throw new error_1.AppError("FILE_BATCH_TOO_MANY", `Batch size ${String(totalCount)} exceeds the 100 limit`);
|
|
135
134
|
}
|
|
136
|
-
const appId =
|
|
135
|
+
const appId = opts.appId;
|
|
137
136
|
// destructive guardrail
|
|
138
137
|
const tty = (0, render_1.isStdoutTty)();
|
|
139
138
|
if (tty && !opts.yes) {
|
|
@@ -38,7 +38,6 @@ const api = __importStar(require("../../../api/index"));
|
|
|
38
38
|
const output_1 = require("../../../utils/output");
|
|
39
39
|
const render_1 = require("../../../utils/render");
|
|
40
40
|
const error_1 = require("../../../utils/error");
|
|
41
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
42
41
|
const index_1 = require("../../../api/file/index");
|
|
43
42
|
const MAX_EXPIRES_SECONDS = 30 * 86400;
|
|
44
43
|
const DEFAULT_EXPIRES_SECONDS = 86400; // 1d(PRD 规定)
|
|
@@ -64,7 +63,7 @@ async function resolveFilePath(appId, input) {
|
|
|
64
63
|
return resolved.file.path;
|
|
65
64
|
}
|
|
66
65
|
async function handleFileSign(file, opts) {
|
|
67
|
-
const appId =
|
|
66
|
+
const appId = opts.appId;
|
|
68
67
|
let expiresSec = DEFAULT_EXPIRES_SECONDS;
|
|
69
68
|
if (opts.expires) {
|
|
70
69
|
expiresSec = (0, render_1.parseDuration)(opts.expires);
|
|
@@ -38,7 +38,6 @@ const api = __importStar(require("../../../api/index"));
|
|
|
38
38
|
const output_1 = require("../../../utils/output");
|
|
39
39
|
const render_1 = require("../../../utils/render");
|
|
40
40
|
const error_1 = require("../../../utils/error");
|
|
41
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
42
41
|
const index_1 = require("../../../api/file/index");
|
|
43
42
|
/**
|
|
44
43
|
* 解析 `<file>` 为后端 head 可用的 filePath。
|
|
@@ -67,7 +66,7 @@ async function resolveFile(appId, input) {
|
|
|
67
66
|
return head;
|
|
68
67
|
}
|
|
69
68
|
async function handleFileStat(file, opts) {
|
|
70
|
-
const appId =
|
|
69
|
+
const appId = opts.appId;
|
|
71
70
|
const info = await resolveFile(appId, file);
|
|
72
71
|
if ((0, output_1.isJsonMode)()) {
|
|
73
72
|
(0, output_1.emitOk)(info);
|
|
@@ -0,0 +1,212 @@
|
|
|
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.handleObservabilityAnalytics = handleObservabilityAnalytics;
|
|
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 GRANULARITY_TO_UNIT = {
|
|
42
|
+
day: "DAY",
|
|
43
|
+
daily: "DAY",
|
|
44
|
+
"1d": "DAY",
|
|
45
|
+
"1h": "HOUR",
|
|
46
|
+
hour: "HOUR",
|
|
47
|
+
week: "WEEK",
|
|
48
|
+
weekly: "WEEK",
|
|
49
|
+
"1w": "WEEK",
|
|
50
|
+
month: "MONTH",
|
|
51
|
+
monthly: "MONTH",
|
|
52
|
+
"1M": "MONTH",
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* <analytics-name> + --series → metricType → 表头列名(=对应 --series 取值)。
|
|
56
|
+
*
|
|
57
|
+
* - users 缺省返回 active/new/total 三条线(多线视图)。
|
|
58
|
+
* - page-view 当前 BAM 只有一个 PAGE_VIEW metricType;不同 series 通过 device_type 过滤区分;
|
|
59
|
+
* 表头 label 直接对应 --series 取值(all-view / desktop-view / mobile-view)。
|
|
60
|
+
*/
|
|
61
|
+
const ANALYTICS_LABELS = {
|
|
62
|
+
users: {
|
|
63
|
+
ACTIVE_USER: "active-users",
|
|
64
|
+
NEW_USER: "new-users",
|
|
65
|
+
TOTAL_USER: "total-users",
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
/** miaoda observability analytics <analytics-name> */
|
|
69
|
+
async function handleObservabilityAnalytics(opts) {
|
|
70
|
+
if (!opts.analyticsName)
|
|
71
|
+
(0, args_1.failArgs)("<analytics-name> 必填");
|
|
72
|
+
const appID = opts.appId;
|
|
73
|
+
const { metricTypes, labelByMetric, extraFilters } = resolveAnalyticsSelection(opts.analyticsName, opts.series);
|
|
74
|
+
const timeAggregationUnit = opts.granularity
|
|
75
|
+
? (GRANULARITY_TO_UNIT[opts.granularity] ?? opts.granularity.toUpperCase())
|
|
76
|
+
: "DAY";
|
|
77
|
+
const nowMs = Date.now();
|
|
78
|
+
const sinceMs = (0, helpers_1.parseToMs)(opts.since) ?? nowMs - 30 * 86_400_000;
|
|
79
|
+
const untilMs = (0, helpers_1.parseToMs)(opts.until) ?? nowMs;
|
|
80
|
+
const bucket = timeAggregationUnit;
|
|
81
|
+
const fieldFilters = (0, helpers_1.buildFieldFilters)([
|
|
82
|
+
{ key: "path", value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
|
|
83
|
+
...extraFilters,
|
|
84
|
+
]);
|
|
85
|
+
const req = {
|
|
86
|
+
appID,
|
|
87
|
+
metricTypes,
|
|
88
|
+
startTimestampNs: (0, helpers_1.msToNs)(sinceMs),
|
|
89
|
+
endTimestampNs: (0, helpers_1.msToNs)((0, helpers_1.ceilMsToBucket)(untilMs, bucket)),
|
|
90
|
+
timeAggregationUnit,
|
|
91
|
+
fieldFilters,
|
|
92
|
+
};
|
|
93
|
+
const resp = await api.observability.queryAnalyticsData(req);
|
|
94
|
+
emitAnalyticsResponse(resp, labelByMetric);
|
|
95
|
+
}
|
|
96
|
+
// ── 渲染 ────────────
|
|
97
|
+
//
|
|
98
|
+
// BAM v1.0.123 起返回 points: [{timestampNs, dimensions, values: {metricType: v}}]——
|
|
99
|
+
// 服务端已把多 metricType 合并到同 timestampNs 的 row,下游直接平铺即可。
|
|
100
|
+
// JSON 模式:原样透传 points(保留 BAM 原 metricType 作为 values 的 key)。
|
|
101
|
+
// Pretty 模式:把 values 里的 metricType 重命名成 CLI label(active-users 等),
|
|
102
|
+
// 再走 buildAnalyticsPivotSchema 渲染。
|
|
103
|
+
function emitAnalyticsResponse(resp, labelByMetric) {
|
|
104
|
+
const points = resp.points ?? [];
|
|
105
|
+
// analytics 不分页,但补齐 has_more / next_cursor 与 log/trace 信封形状对齐
|
|
106
|
+
if ((0, output_1.isJsonMode)()) {
|
|
107
|
+
(0, output_1.emit)({ data: points, next_cursor: null, has_more: false });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const { rows, seriesLabels } = renameAndSort(points, labelByMetric);
|
|
111
|
+
if (rows.length === 0) {
|
|
112
|
+
(0, output_1.emit)({ data: [], next_cursor: null, has_more: false });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
(0, output_1.emit)({ data: rows, next_cursor: null, has_more: false }, buildAnalyticsPivotSchema(seriesLabels));
|
|
116
|
+
}
|
|
117
|
+
function renameAndSort(points, labelByMetric) {
|
|
118
|
+
// 列顺序按 labelByMetric 声明顺序,避免 BAM 返回顺序波动影响表头
|
|
119
|
+
const seriesLabels = Object.values(labelByMetric);
|
|
120
|
+
const rows = points.map((point) => {
|
|
121
|
+
const row = { timestampNs: point.timestampNs };
|
|
122
|
+
for (const [metricType, value] of Object.entries(point.values)) {
|
|
123
|
+
const label = labelByMetric[metricType] ?? metricType;
|
|
124
|
+
row[label] = value;
|
|
125
|
+
}
|
|
126
|
+
return row;
|
|
127
|
+
});
|
|
128
|
+
rows.sort((a, b) => b.timestampNs - a.timestampNs);
|
|
129
|
+
return { rows, seriesLabels };
|
|
130
|
+
}
|
|
131
|
+
function buildAnalyticsPivotSchema(seriesLabels) {
|
|
132
|
+
return {
|
|
133
|
+
columns: [
|
|
134
|
+
{ key: "timestampNs", label: "time", format: output_1.fmt.ns() },
|
|
135
|
+
...seriesLabels.map((label) => ({ key: label, label })),
|
|
136
|
+
],
|
|
137
|
+
strict: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* <analytics-name> + --series → metricTypes[] + label 映射 + 附加过滤
|
|
142
|
+
*
|
|
143
|
+
* users:
|
|
144
|
+
* - 缺省 series → 三条线全返回(active-users/new-users/total-users)
|
|
145
|
+
* - 单值 series → 对应单条线
|
|
146
|
+
* page-view:
|
|
147
|
+
* - 缺省/all-view → 单条 PAGE_VIEW,无 device 过滤,label = "all-view"
|
|
148
|
+
* - desktop-view / mobile-view → 单条 PAGE_VIEW + device_type 过滤,label = --series 值
|
|
149
|
+
*/
|
|
150
|
+
function resolveAnalyticsSelection(cliName, series) {
|
|
151
|
+
const extras = [];
|
|
152
|
+
const normalizedSeries = normalizeAnalyticsSeries(cliName, series);
|
|
153
|
+
if (cliName === "users") {
|
|
154
|
+
if (normalizedSeries === "active-users")
|
|
155
|
+
return single("ACTIVE_USER", "active-users", extras);
|
|
156
|
+
if (normalizedSeries === "new-users")
|
|
157
|
+
return single("NEW_USER", "new-users", extras);
|
|
158
|
+
if (normalizedSeries === "total-users")
|
|
159
|
+
return single("TOTAL_USER", "total-users", extras);
|
|
160
|
+
// 缺省:三条线
|
|
161
|
+
return all(ANALYTICS_LABELS.users, extras);
|
|
162
|
+
}
|
|
163
|
+
if (cliName === "page-view") {
|
|
164
|
+
if (normalizedSeries === "desktop-view") {
|
|
165
|
+
extras.push({ key: "device_type", value: (0, helpers_1.eqFilter)("desktop") });
|
|
166
|
+
return single("PAGE_VIEW", "desktop-view", extras);
|
|
167
|
+
}
|
|
168
|
+
if (normalizedSeries === "mobile-view") {
|
|
169
|
+
extras.push({ key: "device_type", value: (0, helpers_1.eqFilter)("mobile") });
|
|
170
|
+
return single("PAGE_VIEW", "mobile-view", extras);
|
|
171
|
+
}
|
|
172
|
+
// 缺省 / all-view → 不附加 device 过滤
|
|
173
|
+
return single("PAGE_VIEW", "all-view", extras);
|
|
174
|
+
}
|
|
175
|
+
// 兜底:CLI 名直接当 metricType + label
|
|
176
|
+
return single(cliName, cliName, extras);
|
|
177
|
+
}
|
|
178
|
+
function normalizeAnalyticsSeries(cliName, series) {
|
|
179
|
+
if (series === undefined)
|
|
180
|
+
return undefined;
|
|
181
|
+
if (cliName === "users") {
|
|
182
|
+
if (series === "active")
|
|
183
|
+
return "active-users";
|
|
184
|
+
if (series === "new")
|
|
185
|
+
return "new-users";
|
|
186
|
+
if (series === "total")
|
|
187
|
+
return "total-users";
|
|
188
|
+
}
|
|
189
|
+
if (cliName === "page-view") {
|
|
190
|
+
if (series === "all")
|
|
191
|
+
return "all-view";
|
|
192
|
+
if (series === "desktop")
|
|
193
|
+
return "desktop-view";
|
|
194
|
+
if (series === "mobile")
|
|
195
|
+
return "mobile-view";
|
|
196
|
+
}
|
|
197
|
+
return series;
|
|
198
|
+
}
|
|
199
|
+
function single(metricType, label, extras) {
|
|
200
|
+
return {
|
|
201
|
+
metricTypes: [metricType],
|
|
202
|
+
labelByMetric: { [metricType]: label },
|
|
203
|
+
extraFilters: extras,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function all(labelByMetric, extras) {
|
|
207
|
+
return {
|
|
208
|
+
metricTypes: Object.keys(labelByMetric),
|
|
209
|
+
labelByMetric,
|
|
210
|
+
extraFilters: extras,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ceilMsToBucket = exports.floorMsToBucket = exports.msToSec = exports.msToNs = exports.parseToSec = exports.parseToNs = exports.parseToMs = exports.parseTimeToMs = void 0;
|
|
4
|
+
exports.eqFilter = eqFilter;
|
|
5
|
+
exports.rangeFilter = rangeFilter;
|
|
6
|
+
exports.fuzzyFilter = fuzzyFilter;
|
|
7
|
+
exports.buildFieldFilters = buildFieldFilters;
|
|
8
|
+
exports.validateLimit = validateLimit;
|
|
9
|
+
const args_1 = require("../../../utils/args");
|
|
10
|
+
const index_1 = require("../../../api/observability/index");
|
|
11
|
+
// 时间解析助手已抽到 utils/time.ts;这里 re-export 维持 observability handler 的旧 import 路径。
|
|
12
|
+
var time_1 = require("../../../utils/time");
|
|
13
|
+
Object.defineProperty(exports, "parseTimeToMs", { enumerable: true, get: function () { return time_1.parseTimeToMs; } });
|
|
14
|
+
Object.defineProperty(exports, "parseToMs", { enumerable: true, get: function () { return time_1.parseToMs; } });
|
|
15
|
+
Object.defineProperty(exports, "parseToNs", { enumerable: true, get: function () { return time_1.parseToNs; } });
|
|
16
|
+
Object.defineProperty(exports, "parseToSec", { enumerable: true, get: function () { return time_1.parseToSec; } });
|
|
17
|
+
Object.defineProperty(exports, "msToNs", { enumerable: true, get: function () { return time_1.msToNs; } });
|
|
18
|
+
Object.defineProperty(exports, "msToSec", { enumerable: true, get: function () { return time_1.msToSec; } });
|
|
19
|
+
Object.defineProperty(exports, "floorMsToBucket", { enumerable: true, get: function () { return time_1.floorMsToBucket; } });
|
|
20
|
+
Object.defineProperty(exports, "ceilMsToBucket", { enumerable: true, get: function () { return time_1.ceilMsToBucket; } });
|
|
21
|
+
// ── filter 构造 ──
|
|
22
|
+
//
|
|
23
|
+
// BAM FieldFilter 是「类型分桶 + 算子」结构(str/i64/double),不是扁平字段;
|
|
24
|
+
// 字符串字段无 server-side contains,模糊匹配走 fuzzyFilter。
|
|
25
|
+
/** 字符串字段等值过滤 → { str: { eq } } */
|
|
26
|
+
function eqFilter(value, type = "str") {
|
|
27
|
+
const v = type !== "str" ? Number(value) : value;
|
|
28
|
+
return { [type]: { eq: v } };
|
|
29
|
+
}
|
|
30
|
+
/** i64 数值范围过滤 → { i64: { gte, lte } };输入接受 string(CLI flag)或 number */
|
|
31
|
+
function rangeFilter(opts) {
|
|
32
|
+
const i64 = {};
|
|
33
|
+
if (opts.gte !== undefined)
|
|
34
|
+
i64.gte = Number(opts.gte);
|
|
35
|
+
if (opts.lte !== undefined)
|
|
36
|
+
i64.lte = Number(opts.lte);
|
|
37
|
+
return { i64 };
|
|
38
|
+
}
|
|
39
|
+
/** 模糊搜索过滤器;默认 AND 运算符 */
|
|
40
|
+
function fuzzyFilter(keyword, operator = index_1.WordOperator.AND) {
|
|
41
|
+
return { include: { words: [keyword], operator } };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 把若干条件按 key 装到 fieldFilters。值为 undefined 的条目跳过;
|
|
45
|
+
* 全部跳过时返回 undefined(避免传空 map)。
|
|
46
|
+
*/
|
|
47
|
+
function buildFieldFilters(entries) {
|
|
48
|
+
const out = {};
|
|
49
|
+
let added = false;
|
|
50
|
+
for (const { key, value } of entries) {
|
|
51
|
+
if (value === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
out[key] = value;
|
|
54
|
+
added = true;
|
|
55
|
+
}
|
|
56
|
+
return added ? out : undefined;
|
|
57
|
+
}
|
|
58
|
+
// ── 校验 ──
|
|
59
|
+
/** limit 校验(API 限制 1~100) */
|
|
60
|
+
function validateLimit(limit, defaultLimit = 50) {
|
|
61
|
+
const n = Number.isFinite(limit) ? limit : defaultLimit;
|
|
62
|
+
if (n <= 0 || n > 100) {
|
|
63
|
+
(0, args_1.failArgs)(`--limit 必须在 1~100 之间,收到 ${String(n)}`);
|
|
64
|
+
}
|
|
65
|
+
return n;
|
|
66
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleObservabilityAnalytics = exports.handleObservabilityMetric = exports.handleObservabilityTraceGet = exports.handleObservabilityTraceList = exports.handleObservabilityLog = void 0;
|
|
4
|
+
var log_1 = require("./log");
|
|
5
|
+
Object.defineProperty(exports, "handleObservabilityLog", { enumerable: true, get: function () { return log_1.handleObservabilityLog; } });
|
|
6
|
+
var trace_1 = require("./trace");
|
|
7
|
+
Object.defineProperty(exports, "handleObservabilityTraceList", { enumerable: true, get: function () { return trace_1.handleObservabilityTraceList; } });
|
|
8
|
+
Object.defineProperty(exports, "handleObservabilityTraceGet", { enumerable: true, get: function () { return trace_1.handleObservabilityTraceGet; } });
|
|
9
|
+
var metric_1 = require("./metric");
|
|
10
|
+
Object.defineProperty(exports, "handleObservabilityMetric", { enumerable: true, get: function () { return metric_1.handleObservabilityMetric; } });
|
|
11
|
+
var analytics_1 = require("./analytics");
|
|
12
|
+
Object.defineProperty(exports, "handleObservabilityAnalytics", { enumerable: true, get: function () { return analytics_1.handleObservabilityAnalytics; } });
|
|
@@ -0,0 +1,94 @@
|
|
|
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.handleObservabilityLog = handleObservabilityLog;
|
|
37
|
+
const api = __importStar(require("../../../api/index"));
|
|
38
|
+
const output_1 = require("../../../utils/output");
|
|
39
|
+
const index_1 = require("../../../api/observability/index");
|
|
40
|
+
const helpers_1 = require("./helpers");
|
|
41
|
+
/** miaoda observability log */
|
|
42
|
+
async function handleObservabilityLog(opts) {
|
|
43
|
+
const appID = opts.appId;
|
|
44
|
+
const appEnv = "runtime";
|
|
45
|
+
const limit = (0, helpers_1.validateLimit)(opts.limit ?? 50);
|
|
46
|
+
// 过滤 key 对齐 BAM 查询字段名:
|
|
47
|
+
// - 顶层字段:severity_text / trace_id(输出仍映射成 severityText / traceID)
|
|
48
|
+
// - attributes 里的业务字段:module / user_id / page / api / ob_data_id(snake_case,
|
|
49
|
+
// 依据 BAM IDL 的 LogItem.attributes 描述:"包括 tenant_id, module, user_id, page, api 等")
|
|
50
|
+
// - duration_ms 暂未在 BAM 描述里明确列出,留 TODO 等 e2e 验证
|
|
51
|
+
const fieldFilters = (0, helpers_1.buildFieldFilters)([
|
|
52
|
+
{ 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 },
|
|
55
|
+
{ key: "module", value: opts.module ? (0, helpers_1.eqFilter)(opts.module) : undefined },
|
|
56
|
+
{ key: "user_id", value: opts.userId ? (0, helpers_1.eqFilter)(opts.userId, "i64") : undefined },
|
|
57
|
+
{ key: "page", value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
|
|
58
|
+
{ key: "api", value: opts.api ? (0, helpers_1.eqFilter)(opts.api) : undefined },
|
|
59
|
+
{
|
|
60
|
+
// TODO: 确认 BAM 是否真用 duration_ms 作为 attribute key
|
|
61
|
+
key: "duration_ms",
|
|
62
|
+
value: opts.minDuration !== undefined || opts.maxDuration !== undefined
|
|
63
|
+
? (0, helpers_1.rangeFilter)({ gte: opts.minDuration, lte: opts.maxDuration })
|
|
64
|
+
: undefined,
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
const fuzzyFilter = opts.grep
|
|
68
|
+
? {
|
|
69
|
+
include: {
|
|
70
|
+
words: [opts.grep],
|
|
71
|
+
operator: 1, // AND
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
: undefined;
|
|
75
|
+
const req = {
|
|
76
|
+
appID,
|
|
77
|
+
appEnv,
|
|
78
|
+
startTimestampNs: (0, helpers_1.parseToNs)(opts.since),
|
|
79
|
+
endTimestampNs: (0, helpers_1.parseToNs)(opts.until),
|
|
80
|
+
limit,
|
|
81
|
+
pageToken: opts.cursor,
|
|
82
|
+
fieldFilters,
|
|
83
|
+
fuzzyFilter,
|
|
84
|
+
};
|
|
85
|
+
const resp = await api.observability.searchLogs(req);
|
|
86
|
+
emitSearchLogsResponse(resp);
|
|
87
|
+
}
|
|
88
|
+
function emitSearchLogsResponse(resp) {
|
|
89
|
+
(0, output_1.emit)({
|
|
90
|
+
data: resp.logItems ?? [],
|
|
91
|
+
next_cursor: resp.hasMore ? resp.nextPageToken : null,
|
|
92
|
+
has_more: resp.hasMore,
|
|
93
|
+
}, index_1.logItemSchema);
|
|
94
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
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.handleObservabilityMetric = handleObservabilityMetric;
|
|
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
|
+
/**
|
|
42
|
+
* PRD <metric-name> + --series → 服务端 metric name → 表头列名(=对应 --series 取值)。
|
|
43
|
+
*
|
|
44
|
+
* - 缺省 --series 时返回该 cliName 下所有相关线。
|
|
45
|
+
* - latency / requests 的 error 线 v1.0.122 起由独立 metric 提供(不再 is_error filter)。
|
|
46
|
+
*
|
|
47
|
+
* 表头标签直接用 --series 名("total"/"error"/"p50"/"p99");单一指标则用 cliName。
|
|
48
|
+
*/
|
|
49
|
+
const METRIC_LABELS = {
|
|
50
|
+
cpu: { cpu_usage: "cpu" },
|
|
51
|
+
memory: { mem_usage: "memory" },
|
|
52
|
+
requests: {
|
|
53
|
+
client_api_request_count: "total",
|
|
54
|
+
client_api_request_error_count: "error",
|
|
55
|
+
},
|
|
56
|
+
latency: {
|
|
57
|
+
client_api_request_latency_p50: "p50",
|
|
58
|
+
client_api_request_latency_p99: "p99",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
/** miaoda observability metric <metric-name> */
|
|
62
|
+
async function handleObservabilityMetric(opts) {
|
|
63
|
+
if (!opts.metricName)
|
|
64
|
+
(0, args_1.failArgs)("<metric-name> 必填");
|
|
65
|
+
const appID = opts.appId;
|
|
66
|
+
const { metricNames, labelByMetric, extraFilters } = resolveMetricSelection(opts.metricName, opts.series);
|
|
67
|
+
const filters = (0, helpers_1.buildFieldFilters)([
|
|
68
|
+
{ key: "referer_path", value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
|
|
69
|
+
{ key: "api", value: opts.api ? (0, helpers_1.eqFilter)(opts.api) : undefined },
|
|
70
|
+
...extraFilters,
|
|
71
|
+
]);
|
|
72
|
+
// since/until 直接以秒透传到 BAM;当前未做桶对齐(如需开启把 until
|
|
73
|
+
// 用 ceilMsToBucket(untilMs, downSample as GranularityBucket) 包一层即可)。
|
|
74
|
+
const downSample = opts.downSample ?? "1m";
|
|
75
|
+
const nowMs = Date.now();
|
|
76
|
+
const sinceMs = (0, helpers_1.parseToMs)(opts.since) ?? nowMs - 30 * 86_400_000;
|
|
77
|
+
const untilMs = (0, helpers_1.parseToMs)(opts.until) ?? nowMs;
|
|
78
|
+
const req = {
|
|
79
|
+
appID,
|
|
80
|
+
appEnv: "runtime",
|
|
81
|
+
metricNames,
|
|
82
|
+
startTimestamp: (0, helpers_1.msToSec)(sinceMs),
|
|
83
|
+
endTimestamp: (0, helpers_1.msToSec)(untilMs),
|
|
84
|
+
filters,
|
|
85
|
+
downSample,
|
|
86
|
+
needPackLackPoint: false,
|
|
87
|
+
};
|
|
88
|
+
const resp = await api.observability.queryMetricsData(req);
|
|
89
|
+
// requests 的 count / error_count:
|
|
90
|
+
// - BAM 在该时间点无数据时会传 null,或者 values 里干脆缺 key
|
|
91
|
+
// - CLI 按"请求过的 metricNames"逐一兜底为 0,让"没有请求"和"没有数据"
|
|
92
|
+
// 在表格 / JSON 里都显式呈现成 0,避免空格 / 缺字段被误解为缺失
|
|
93
|
+
// latency / cpu / memory 不做这个兜底——p50 没数据 ≠ 延迟为 0。
|
|
94
|
+
if (opts.metricName === "requests") {
|
|
95
|
+
fillRequestedMetricsWithZero(resp, metricNames);
|
|
96
|
+
}
|
|
97
|
+
emitMetricsResponse(resp, labelByMetric);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 按"客户端请求过的 metricNames"兜底缺失/为 null 的值为 0;不去动 BAM 多返回的
|
|
101
|
+
* 其他字段(防御性,避免误改不属于本次查询的数据)。
|
|
102
|
+
*/
|
|
103
|
+
function fillRequestedMetricsWithZero(resp, requestedMetricNames) {
|
|
104
|
+
for (const point of resp.points ?? []) {
|
|
105
|
+
for (const name of requestedMetricNames) {
|
|
106
|
+
point.values[name] ??= 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// ── 渲染 ─────────
|
|
111
|
+
//
|
|
112
|
+
// BAM v1.0.123 起返回 points: [{timestamp, dimensions, values: {metricName: v}}]——
|
|
113
|
+
// 服务端已把多 metric 合并到同 timestamp 的 row,下游直接平铺即可。
|
|
114
|
+
// JSON 模式:原样透传 points(保留 BAM 原 metricName 作为 values 的 key)。
|
|
115
|
+
// Pretty 模式:把 values 里的 metricName 重命名成 CLI label(p50/p99/total 等),
|
|
116
|
+
// 再走 buildPivotSchema 渲染。
|
|
117
|
+
function emitMetricsResponse(resp, labelByMetric) {
|
|
118
|
+
const points = resp.points ?? [];
|
|
119
|
+
// metric 不分页,但仍补齐 has_more / next_cursor 以对齐 log/trace 的信封形状
|
|
120
|
+
if ((0, output_1.isJsonMode)()) {
|
|
121
|
+
(0, output_1.emit)({ data: points, next_cursor: null, has_more: false });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const { rows, seriesLabels } = renameAndSort(points, labelByMetric);
|
|
125
|
+
if (rows.length === 0) {
|
|
126
|
+
(0, output_1.emit)({ data: [], next_cursor: null, has_more: false });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const hasLatency = Object.keys(labelByMetric).some((n) => n.includes("latency"));
|
|
130
|
+
(0, output_1.emit)({ data: rows, next_cursor: null, has_more: false }, buildPivotSchema(seriesLabels, hasLatency));
|
|
131
|
+
}
|
|
132
|
+
function renameAndSort(points, labelByMetric) {
|
|
133
|
+
// 列顺序按 labelByMetric 声明顺序,避免 BAM 返回顺序波动影响表头
|
|
134
|
+
const seriesLabels = Object.values(labelByMetric);
|
|
135
|
+
// TODO: 当 dimensions 非空(groupBy 多 series)时,把 dimensions 也并入 row
|
|
136
|
+
const rows = points.map((point) => {
|
|
137
|
+
const row = { timestamp: point.timestamp };
|
|
138
|
+
for (const [metricName, value] of Object.entries(point.values)) {
|
|
139
|
+
const label = labelByMetric[metricName] ?? metricName;
|
|
140
|
+
row[label] = value;
|
|
141
|
+
}
|
|
142
|
+
return row;
|
|
143
|
+
});
|
|
144
|
+
// 按时间倒序,与 log/trace 的"最新在前"风格一致
|
|
145
|
+
rows.sort((a, b) => b.timestamp - a.timestamp);
|
|
146
|
+
return { rows, seriesLabels };
|
|
147
|
+
}
|
|
148
|
+
function buildPivotSchema(seriesLabels, hasLatency) {
|
|
149
|
+
const valueFormat = hasLatency ? output_1.fmt.durationMs() : undefined;
|
|
150
|
+
return {
|
|
151
|
+
columns: [
|
|
152
|
+
{ key: "timestamp", label: "time", format: output_1.fmt.sec() },
|
|
153
|
+
...seriesLabels.map((label) => ({
|
|
154
|
+
key: label,
|
|
155
|
+
label,
|
|
156
|
+
...(valueFormat ? { format: valueFormat } : {}),
|
|
157
|
+
})),
|
|
158
|
+
],
|
|
159
|
+
strict: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 解析 <metric-name> + --series 的组合:决定最终 metricNames、表头 label 映射、附加过滤。
|
|
164
|
+
*
|
|
165
|
+
* label 直接用 --series 取值("total"/"error"/"p50"/"p99");单一指标 cliName 也作为 label。
|
|
166
|
+
*/
|
|
167
|
+
function resolveMetricSelection(cliName, series) {
|
|
168
|
+
const extras = [];
|
|
169
|
+
if (cliName === "latency") {
|
|
170
|
+
if (series === "p99") {
|
|
171
|
+
return single("client_api_request_latency_p99", "p99", extras);
|
|
172
|
+
}
|
|
173
|
+
if (series === "p50") {
|
|
174
|
+
return single("client_api_request_latency_p50", "p50", extras);
|
|
175
|
+
}
|
|
176
|
+
return all(METRIC_LABELS.latency, extras);
|
|
177
|
+
}
|
|
178
|
+
if (cliName === "requests") {
|
|
179
|
+
// v1.0.122 起 error 线由独立 metric 提供,不再走 is_error filter
|
|
180
|
+
if (series === "total") {
|
|
181
|
+
return single("client_api_request_count", "total", extras);
|
|
182
|
+
}
|
|
183
|
+
if (series === "error") {
|
|
184
|
+
return single("client_api_request_error_count", "error", extras);
|
|
185
|
+
}
|
|
186
|
+
return all(METRIC_LABELS.requests, extras);
|
|
187
|
+
}
|
|
188
|
+
if (cliName === "cpu")
|
|
189
|
+
return all(METRIC_LABELS.cpu, extras);
|
|
190
|
+
if (cliName === "memory")
|
|
191
|
+
return all(METRIC_LABELS.memory, extras);
|
|
192
|
+
// 兜底:CLI 名直接作为 metric name + label
|
|
193
|
+
return single(cliName, cliName, extras);
|
|
194
|
+
}
|
|
195
|
+
function single(metricName, label, extras) {
|
|
196
|
+
return {
|
|
197
|
+
metricNames: [metricName],
|
|
198
|
+
labelByMetric: { [metricName]: label },
|
|
199
|
+
extraFilters: extras,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function all(labelByMetric, extras) {
|
|
203
|
+
return {
|
|
204
|
+
metricNames: Object.keys(labelByMetric),
|
|
205
|
+
labelByMetric,
|
|
206
|
+
extraFilters: extras,
|
|
207
|
+
};
|
|
208
|
+
}
|