@blueking/open-telemetry 0.0.15 → 0.0.17
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 +83 -244
- package/dist/bk-rum.global.js +2 -2
- package/dist/bk-rum.global.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/metric.d.ts +2 -0
- package/dist/core/sdk.d.ts +4 -0
- package/dist/core/signal.d.ts +3 -0
- package/dist/core/telemetry-attributes.d.ts +1 -0
- package/dist/core/throttle.d.ts +2 -2
- package/dist/index.js +525 -456
- package/dist/index.js.map +1 -1
- package/dist/plugins/blank-screen.d.ts +4 -0
- package/dist/plugins/common.d.ts +1 -0
- package/dist/plugins/csp-violation.d.ts +1 -1
- package/dist/plugins/long-task.d.ts +4 -0
- package/dist/plugins/request-body.d.ts +2 -1
- package/dist/plugins/resource.d.ts +4 -0
- package/dist/plugins/web-vitals.d.ts +2 -0
- package/package.json +1 -1
- package/skills/bk-rum-otele/SKILL.md +8 -3
- package/skills/bk-rum-otele/evals/evals.json +3 -3
- package/skills/bk-rum-otele/references/api.md +236 -135
- package/skills/bk-rum-otele/references/architecture.md +141 -0
- package/skills/bk-rum-otele/references/capabilities.md +100 -0
- package/skills/bk-rum-otele/references/concepts.md +7 -4
- package/skills/bk-rum-otele/references/configuration.md +221 -178
- package/skills/bk-rum-otele/references/custom-plugin.md +15 -3
- package/skills/bk-rum-otele/references/data-model.md +5 -3
- package/skills/bk-rum-otele/references/getting-started.md +16 -0
- package/skills/bk-rum-otele/references/plugins.md +100 -72
- package/skills/bk-rum-otele/references/privacy.md +5 -1
- package/skills/bk-rum-otele/references/telemetry-reference.md +14 -12
- package/skills/bk-rum-otele/references/transport-sampling.md +79 -0
package/README.md
CHANGED
|
@@ -1,42 +1,68 @@
|
|
|
1
1
|
# @blueking/open-telemetry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
面向浏览器真实用户监控(RUM)的 OpenTelemetry SDK。它把 Session、View、Fetch/XHR、静态资源、错误、Web Vitals、用户操作、白屏、Long Task、WebSocket、CSP 和设备信息统一为 Trace、Metric、Log,并通过 OTLP HTTP 上报。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 快速接入
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install @blueking/open-telemetry
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```javascript
|
|
11
|
+
```ts
|
|
14
12
|
import { BkOpenTelemetry } from '@blueking/open-telemetry';
|
|
15
13
|
|
|
16
|
-
const bkOT = new BkOpenTelemetry({
|
|
14
|
+
export const bkOT = new BkOpenTelemetry({
|
|
17
15
|
application: {
|
|
18
16
|
name: 'order-center',
|
|
19
17
|
environment: 'production',
|
|
20
18
|
version: '1.2.0',
|
|
21
19
|
},
|
|
22
20
|
transport: {
|
|
23
|
-
endpoint: 'https://
|
|
24
|
-
token: '
|
|
21
|
+
endpoint: 'https://rum-collector.example.com',
|
|
22
|
+
token: 'report-token',
|
|
23
|
+
},
|
|
24
|
+
session: {
|
|
25
|
+
sampleRate: 0.2,
|
|
26
|
+
},
|
|
27
|
+
tracking: {
|
|
28
|
+
view: {
|
|
29
|
+
getPathGroup: url => new URL(url, location.href).pathname.replace(/\/orders\/[^/]+/, '/orders/:id'),
|
|
30
|
+
},
|
|
25
31
|
},
|
|
26
32
|
});
|
|
27
33
|
```
|
|
28
34
|
|
|
29
|
-
`application.name`
|
|
35
|
+
`application.name` 与 `transport` 必填。根 `transport.endpoint` 会生成三个上报地址:
|
|
36
|
+
|
|
37
|
+
| 信号 | 默认地址 | 主要用途 |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| Trace | `<endpoint>/v1/rum` | View、请求、资源、错误、性能、Action |
|
|
40
|
+
| Metric | `<endpoint>/v1/metrics` | Web Vitals、View Loading、白屏、Long Task、WebSocket 聚合 |
|
|
41
|
+
| Log | `<endpoint>/v1/logs` | Session 生命周期、View Snapshot、请求 Body、WebSocket 事件 |
|
|
42
|
+
|
|
43
|
+
也可以为 `transport.signals.traces|metrics|logs.endpoint` 分别提供完整 URL,或把不需要的信号设为 `false`。至少要启用一种信号。
|
|
44
|
+
|
|
45
|
+
## 默认能力
|
|
46
|
+
|
|
47
|
+
只提供最小配置时,SDK 会自动启动并建立 Session 与 View。
|
|
30
48
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
| 能力 | 默认 | 产出 |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Session / View | 开启且不可关闭 | `session.*` Log、`browser.view` Span/Log/Metric、运行时关联字段 |
|
|
52
|
+
| Fetch / XHR | 开启 | `browser.resource` CLIENT Span,可选 `traceparent` 与 Body Log |
|
|
53
|
+
| 静态资源 | 开启 | `browser.resource` Span、网络阶段属性 |
|
|
54
|
+
| Error | 开启 | JS、Promise、资源加载错误 Span |
|
|
55
|
+
| Web Vitals | 开启 | CLS/FCP/INP/LCP/TTFB Span 与 Metric |
|
|
56
|
+
| Action | 开启 | 点击活动、耗时、关联计数和挫败信号 Span |
|
|
57
|
+
| Blank Screen | 开启 | 白屏 Span 与 Counter |
|
|
58
|
+
| Device | 开启 | 浏览器、OS、屏幕、网络运行时属性 |
|
|
59
|
+
| Long Task / WebSocket / CSP | 关闭 | 按需通过 `tracking` 开启 |
|
|
34
60
|
|
|
35
|
-
|
|
61
|
+
完整的浏览器 API、默认值、配置入口和遥测产出见[能力矩阵](./wikis/capabilities.md)。
|
|
36
62
|
|
|
37
|
-
##
|
|
63
|
+
## 生产配置基线
|
|
38
64
|
|
|
39
|
-
```
|
|
65
|
+
```ts
|
|
40
66
|
const bkOT = new BkOpenTelemetry({
|
|
41
67
|
application: {
|
|
42
68
|
name: 'order-center',
|
|
@@ -44,205 +70,56 @@ const bkOT = new BkOpenTelemetry({
|
|
|
44
70
|
version: '1.2.0',
|
|
45
71
|
},
|
|
46
72
|
transport: {
|
|
47
|
-
endpoint: 'https://
|
|
48
|
-
token: '
|
|
73
|
+
endpoint: 'https://rum-collector.example.com',
|
|
74
|
+
token: 'report-token',
|
|
49
75
|
signals: {
|
|
50
76
|
traces: {
|
|
51
77
|
processor: 'batch',
|
|
52
|
-
batch: {
|
|
53
|
-
maxQueueSize: 2048,
|
|
54
|
-
maxExportBatchSize: 512,
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
metrics: {
|
|
58
|
-
intervalMillis: 60_000,
|
|
78
|
+
batch: { maxQueueSize: 2048, maxExportBatchSize: 512 },
|
|
59
79
|
},
|
|
80
|
+
metrics: { intervalMillis: 60_000 },
|
|
60
81
|
},
|
|
61
82
|
},
|
|
62
|
-
session: {
|
|
63
|
-
sampleRate: 0.2,
|
|
64
|
-
},
|
|
83
|
+
session: { sampleRate: 0.2 },
|
|
65
84
|
tracking: {
|
|
66
85
|
view: {
|
|
67
|
-
getPathGroup: url => new URL(url, location.href).pathname.replace(/\/orders
|
|
68
|
-
excludedActivityUrls: [/\/events\/stream
|
|
86
|
+
getPathGroup: url => new URL(url, location.href).pathname.replace(/\/orders\/[^/]+/, '/orders/:id'),
|
|
87
|
+
excludedActivityUrls: [/\/events\/stream(?:\?|$)/],
|
|
69
88
|
},
|
|
70
89
|
request: {
|
|
71
|
-
allowedTracingUrls: ['https://api.example.com'],
|
|
90
|
+
allowedTracingUrls: [url => new URL(url).origin === 'https://api.example.com'],
|
|
72
91
|
excludedUrls: [/\/health(?:\?|$)/],
|
|
73
92
|
},
|
|
74
|
-
|
|
75
|
-
websocket: true,
|
|
93
|
+
blankScreen: { rootSelector: '#app' },
|
|
76
94
|
},
|
|
77
95
|
context: {
|
|
78
96
|
user: { id: window.USER_ID },
|
|
79
97
|
attributes: {
|
|
80
98
|
page: () => ({ 'biz.module': 'order' }),
|
|
81
|
-
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
privacy: {
|
|
85
|
-
redactAttributes: attributes => attributes,
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## 入口字段
|
|
91
|
-
|
|
92
|
-
| 字段 | 作用 |
|
|
93
|
-
| --- | --- |
|
|
94
|
-
| `application` | 应用名、环境和版本;`name` 必填 |
|
|
95
|
-
| `transport` | OTLP endpoint、鉴权头及每类 signal 配置;根 `endpoint` 与单信号 endpoint 至少提供一种 |
|
|
96
|
-
| `session` | Session storage key 和确定性 `sampleRate` |
|
|
97
|
-
| `tracking` | 内置采集能力、View 分组、请求过滤和全局 `beforeSend` |
|
|
98
|
-
| `context` | 用户及 page/metric/error/custom 公共属性 |
|
|
99
|
-
| `privacy` | Consent、URL 和属性脱敏 |
|
|
100
|
-
| `extensions` | 自定义插件与原生 OpenTelemetry instrumentation |
|
|
101
|
-
|
|
102
|
-
旧入口 `app`、`sampling`、`route`、`view`、`plugins`、`instrumentations`、`attributes`、`spanBatch` 不再支持。
|
|
103
|
-
|
|
104
|
-
## 三类信号独立开关
|
|
105
|
-
|
|
106
|
-
`traces`、`metrics`、`logs` 默认全部开启。设为 `false` 会关闭对应导出链路;关闭的信号不会创建 exporter、processor 或 metric reader。
|
|
107
|
-
|
|
108
|
-
```javascript
|
|
109
|
-
new BkOpenTelemetry({
|
|
110
|
-
application: { name: 'trace-only-app' },
|
|
111
|
-
transport: {
|
|
112
|
-
signals: {
|
|
113
|
-
traces: { endpoint: 'https://trace.example.com/v1/rum' },
|
|
114
|
-
metrics: false,
|
|
115
|
-
logs: false,
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
根 `transport.endpoint` 可省略,但每个开启的信号必须配置独立 endpoint;三类信号不能全部关闭。关闭 Trace 后不会创建 Span,也不会注入 `traceparent`。
|
|
122
|
-
|
|
123
|
-
## View 与 SPA
|
|
124
|
-
|
|
125
|
-
自动模式会为初始加载、pathname/hash 路由、BFCache 和 Session 续期创建独立 View。query-only 和页内锚点变化不会创建新 View。
|
|
126
|
-
|
|
127
|
-
```javascript
|
|
128
|
-
const bkOT = new BkOpenTelemetry({
|
|
129
|
-
application: { name: 'manual-view-app' },
|
|
130
|
-
transport: { endpoint: 'https://your-otlp-collector' },
|
|
131
|
-
tracking: {
|
|
132
|
-
view: { tracking: 'manual' },
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
bkOT.startView({ name: '订单详情', url: location.href });
|
|
137
|
-
bkOT.setViewName('订单详情-只读');
|
|
138
|
-
bkOT.setViewLoadingTime();
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
每个 View 有一个覆盖生命周期的 `browser.view` span,并产生 create/update/final Snapshot log。初始 View 记录 TTFB、DOM Interactive、DOMContentLoaded、DOM Complete、Load Event;SPA View 独立计算 CLS session window 和 INP p98。
|
|
142
|
-
|
|
143
|
-
## Metric 如何关联 View
|
|
144
|
-
|
|
145
|
-
Metric datapoint 只携带低基数 `view.url_path_group`,不携带唯一 `view.id`,避免时序基数随 PV 无限增长。
|
|
146
|
-
|
|
147
|
-
精确关联放在 View Snapshot、span 和 log:这些事件会携带 `session.id + view.id`,用户操作期间还会携带 `action.id`。需要从单个指标下钻到具体 View 时,应在后端使用 exemplar,或从原始事件派生聚合指标。
|
|
148
|
-
|
|
149
|
-
## 请求与资源
|
|
150
|
-
|
|
151
|
-
Fetch/XHR 由一套请求生命周期统一采集,不会重复 patch 或重复计数。Trace Header 只对 `allowedTracingUrls` 命中的 URL 注入,包括同源请求。
|
|
152
|
-
|
|
153
|
-
```javascript
|
|
154
|
-
new BkOpenTelemetry({
|
|
155
|
-
application: { name: 'request-demo' },
|
|
156
|
-
transport: { endpoint: 'https://your-otlp-collector' },
|
|
157
|
-
tracking: {
|
|
158
|
-
request: {
|
|
159
|
-
allowedTracingUrls: ['https://api.example.com'],
|
|
160
|
-
excludedUrls: [/\/health(?:\?|$)/],
|
|
161
|
-
beforeSend: (event, context) => {
|
|
162
|
-
event.attributes['request.transport'] = context.type;
|
|
163
|
-
return event.resource.statusCode !== 404;
|
|
164
|
-
},
|
|
165
|
-
body: {
|
|
166
|
-
capture: 'errors',
|
|
167
|
-
maxSize: 10 * 1024,
|
|
168
|
-
redact: ({ body }) => body.replace(/("(?:password|token)"\s*:\s*)"[^"]*"/g, '$1"***"'),
|
|
169
|
-
},
|
|
99
|
+
metric: () => ({ 'biz.app': 'order-center' }),
|
|
170
100
|
},
|
|
171
101
|
},
|
|
172
|
-
});
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
静态资源与请求共用一个 Resource PerformanceObserver。静态资源生成独立 span;Fetch/XHR timing 会匹配回对应请求 span。SDK 自身 OTLP 请求始终排除。
|
|
176
|
-
|
|
177
|
-
## 隐私与 Consent
|
|
178
|
-
|
|
179
|
-
默认 URL 脱敏会移除用户名、密码、query 和 hash。Action 默认只读取显式属性及可访问名称,不读取任意 DOM 文本;设备 ID 默认不持久化。
|
|
180
|
-
|
|
181
|
-
```javascript
|
|
182
|
-
const bkOT = new BkOpenTelemetry({
|
|
183
|
-
application: { name: 'privacy-demo' },
|
|
184
|
-
transport: { endpoint: 'https://your-otlp-collector' },
|
|
185
102
|
privacy: {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
await bkOT.setTrackingConsent('granted');
|
|
193
|
-
await bkOT.setTrackingConsent('not-granted');
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
未授权时不创建 Session、不安装采集 patch。运行期可授权、撤回和再次授权。
|
|
197
|
-
|
|
198
|
-
## 全局 beforeSend
|
|
199
|
-
|
|
200
|
-
`tracking.beforeSend` 在 span/log 进入 exporter 前执行,可修改属性或返回 `false` 丢弃事件。
|
|
201
|
-
|
|
202
|
-
```javascript
|
|
203
|
-
new BkOpenTelemetry({
|
|
204
|
-
application: { name: 'filter-demo' },
|
|
205
|
-
transport: { endpoint: 'https://your-otlp-collector' },
|
|
206
|
-
tracking: {
|
|
207
|
-
beforeSend(event) {
|
|
208
|
-
delete event.attributes['debug.payload'];
|
|
209
|
-
return event.name !== 'custom.ignore-me';
|
|
103
|
+
redactAttributes(attributes) {
|
|
104
|
+
const next = { ...attributes };
|
|
105
|
+
delete next['biz.secret'];
|
|
106
|
+
return next;
|
|
210
107
|
},
|
|
211
108
|
},
|
|
212
109
|
});
|
|
213
110
|
```
|
|
214
111
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
## Session 与采样
|
|
112
|
+
生产接入至少确认以下事项:
|
|
218
113
|
|
|
219
|
-
|
|
114
|
+
1. `getPathGroup` 已把动态 ID 转成低基数路由模板。
|
|
115
|
+
2. 跨域 Trace Header 白名单足够精确,服务端 CORS 已放行 `traceparent`。
|
|
116
|
+
3. `user.id`、URL、错误和业务属性满足隐私要求;HTTP Body 默认保持关闭。
|
|
117
|
+
4. SPA 的 `blankScreen.rootSelector` 指向真实挂载节点。
|
|
118
|
+
5. 同一页面只运行一个实例,避免重复 patch Fetch/XHR/WebSocket。
|
|
220
119
|
|
|
221
|
-
|
|
120
|
+
## 手动事件与 View
|
|
222
121
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
```javascript
|
|
226
|
-
new BkOpenTelemetry({
|
|
227
|
-
application: { name: 'interaction-demo' },
|
|
228
|
-
transport: { endpoint: 'https://your-otlp-collector' },
|
|
229
|
-
tracking: {
|
|
230
|
-
action: {
|
|
231
|
-
actionNameAttribute: 'data-bk-action-name',
|
|
232
|
-
nameCollection: 'explicit',
|
|
233
|
-
},
|
|
234
|
-
device: { persistentId: false },
|
|
235
|
-
blankScreen: { rootSelector: '#app' },
|
|
236
|
-
cspViolation: true,
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
只有明确接受 DOM 文本采集风险时,才设置 `action.nameCollection: 'text'`。只有需要跨 Session 设备关联时,才设置 `device.persistentId: true`。
|
|
242
|
-
|
|
243
|
-
## 手动事件
|
|
244
|
-
|
|
245
|
-
```javascript
|
|
122
|
+
```ts
|
|
246
123
|
bkOT.setUser({ id: 'user-001' });
|
|
247
124
|
|
|
248
125
|
bkOT.reportAction({
|
|
@@ -256,71 +133,33 @@ bkOT.reportCustomEvent({
|
|
|
256
133
|
});
|
|
257
134
|
```
|
|
258
135
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
```javascript
|
|
262
|
-
import { BkOpenTelemetry, createPlugin } from '@blueking/open-telemetry';
|
|
263
|
-
|
|
264
|
-
const plugin = createPlugin({
|
|
265
|
-
name: 'demo-plugin',
|
|
266
|
-
init(context) {
|
|
267
|
-
const span = context.startSpan('demo.plugin_ready');
|
|
268
|
-
span.end();
|
|
269
|
-
},
|
|
270
|
-
});
|
|
136
|
+
手动 View 最稳妥的接入方式是在 SDK 启动并建立 Session 后创建:
|
|
271
137
|
|
|
138
|
+
```ts
|
|
272
139
|
const bkOT = new BkOpenTelemetry({
|
|
273
|
-
application: { name: '
|
|
140
|
+
application: { name: 'manual-view-app' },
|
|
274
141
|
autoStart: false,
|
|
275
|
-
transport: { endpoint: 'https://
|
|
276
|
-
|
|
277
|
-
plugins: [plugin],
|
|
278
|
-
instrumentations: [],
|
|
279
|
-
},
|
|
142
|
+
transport: { endpoint: 'https://rum-collector.example.com' },
|
|
143
|
+
tracking: { view: { tracking: 'manual' } },
|
|
280
144
|
});
|
|
281
145
|
|
|
282
146
|
await bkOT.start();
|
|
283
|
-
|
|
284
|
-
await bkOT.shutdown();
|
|
147
|
+
bkOT.startView({ name: '订单详情', url: location.href });
|
|
285
148
|
```
|
|
286
149
|
|
|
287
|
-
|
|
150
|
+
`reportAction()`、`reportCustomEvent()` 和 View 控制方法均受 Session 采样/有效状态约束。调试时先使用 `session.sampleRate: 1`。
|
|
288
151
|
|
|
289
|
-
##
|
|
152
|
+
## 文档导航
|
|
290
153
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
},
|
|
304
|
-
session: { sampleRate: 1, storageKey: 'optional-session-key' },
|
|
305
|
-
tracking: {
|
|
306
|
-
view: { tracking: 'auto', loadingTime: true, getPathGroup: url => new URL(url, location.href).pathname },
|
|
307
|
-
request: {},
|
|
308
|
-
resource: true,
|
|
309
|
-
error: true,
|
|
310
|
-
webVitals: true,
|
|
311
|
-
blankScreen: true,
|
|
312
|
-
device: true,
|
|
313
|
-
action: true,
|
|
314
|
-
longTask: false,
|
|
315
|
-
cspViolation: false,
|
|
316
|
-
websocket: false,
|
|
317
|
-
beforeSend: event => true,
|
|
318
|
-
},
|
|
319
|
-
context: { user: { id: 'user-001' }, attributes: {} },
|
|
320
|
-
privacy: { trackingConsent: 'granted' },
|
|
321
|
-
extensions: { plugins: [], instrumentations: [] },
|
|
322
|
-
autoStart: true,
|
|
323
|
-
enabled: true,
|
|
324
|
-
debug: false,
|
|
325
|
-
});
|
|
326
|
-
```
|
|
154
|
+
| 目标 | 文档 |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| 首次接入 | [快速上手](./wikis/getting-started.md) |
|
|
157
|
+
| 判断能采什么、默认是否开启、产出哪类信号 | [能力矩阵](./wikis/capabilities.md) |
|
|
158
|
+
| 理解初始化、插件顺序、上下文、过滤和上报数据流 | [架构与数据流](./wikis/architecture.md) |
|
|
159
|
+
| 查完整配置、默认值和约束 | [配置参考](./wikis/configuration.md) |
|
|
160
|
+
| 查公开方法与导出符号 | [API 参考](./wikis/api.md) |
|
|
161
|
+
| 查精确 Span / Metric / Log 名和字段 | [遥测参考](./wikis/telemetry-reference.md) |
|
|
162
|
+
| 隐私、Consent 和低基数字段 | [隐私与公共属性](./wikis/privacy-attributes.md) |
|
|
163
|
+
| 没有数据或数据异常 | [调试与排障](./wikis/troubleshooting.md) |
|
|
164
|
+
|
|
165
|
+
完整 Wiki 入口见 [`wikis/`](./wikis/README.md)。文档以当前 `src/` 和 `src/index.ts` 公开导出为事实来源,不承诺历史配置兼容。
|