@spectratools/cli-shared 0.2.0 → 0.4.0
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 +85 -0
- package/dist/{chunk-TKORXDDX.js → chunk-BBONIM34.js} +30 -8
- package/dist/chunk-G7AGMLZE.js +136 -0
- package/dist/{chunk-74BQLM22.js → chunk-I2D7COI7.js} +35 -4
- package/dist/{chunk-YWGC33UJ.js → chunk-S4AQMOWL.js} +1 -73
- package/dist/index.d.ts +1 -1
- package/dist/index.js +21 -7
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +3 -2
- package/dist/telemetry/index.d.ts +41 -2
- package/dist/telemetry/index.js +17 -3
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -211,8 +211,93 @@ await server.close();
|
|
|
211
211
|
|
|
212
212
|
---
|
|
213
213
|
|
|
214
|
+
## Telemetry (OpenTelemetry)
|
|
215
|
+
|
|
216
|
+
Built-in OpenTelemetry instrumentation for distributed tracing and metrics. Zero overhead when disabled — OTEL modules are lazy-loaded only when telemetry is active.
|
|
217
|
+
|
|
218
|
+
### Initialize telemetry
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { initTelemetry, shutdownTelemetry } from '@spectra-the-bot/cli-shared/telemetry';
|
|
222
|
+
|
|
223
|
+
// Initialize at CLI startup — no-op if OTEL env vars are not set
|
|
224
|
+
initTelemetry('my-cli');
|
|
225
|
+
|
|
226
|
+
// Shut down gracefully at exit — safe to call even if never initialized
|
|
227
|
+
await shutdownTelemetry();
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Telemetry is enabled when `OTEL_EXPORTER_OTLP_ENDPOINT` is set or `SPECTRA_OTEL_ENABLED=true`.
|
|
231
|
+
|
|
232
|
+
### Command spans
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { createCommandSpan, withCommandSpan } from '@spectra-the-bot/cli-shared/telemetry';
|
|
236
|
+
|
|
237
|
+
// Option 1: Manual span management
|
|
238
|
+
const span = createCommandSpan('account balance', { address: '0x...', format: 'json' });
|
|
239
|
+
try {
|
|
240
|
+
// ... execute command
|
|
241
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
242
|
+
} catch (err) {
|
|
243
|
+
recordError(span, err);
|
|
244
|
+
throw err;
|
|
245
|
+
} finally {
|
|
246
|
+
span.end();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Option 2: Automatic span lifecycle
|
|
250
|
+
const result = await withCommandSpan('account balance', { address: '0x...' }, async () => {
|
|
251
|
+
// ... execute command
|
|
252
|
+
return data;
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Child spans
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { withSpan } from '@spectra-the-bot/cli-shared/telemetry';
|
|
260
|
+
|
|
261
|
+
const result = await withSpan('parse-response', async (span) => {
|
|
262
|
+
// span is automatically ended and errors are recorded
|
|
263
|
+
return parseData(raw);
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### HTTP spans (automatic)
|
|
268
|
+
|
|
269
|
+
The `createHttpClient` utility automatically creates child spans for every HTTP request when OTEL is active:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { createHttpClient } from '@spectra-the-bot/cli-shared/utils';
|
|
273
|
+
|
|
274
|
+
const client = createHttpClient({ baseUrl: 'https://api.example.com' });
|
|
275
|
+
|
|
276
|
+
// This request automatically creates an HTTP span with method, URL, and status code
|
|
277
|
+
const data = await client.request('/v1/items');
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Attribute sanitization
|
|
281
|
+
|
|
282
|
+
Span attributes are automatically sanitized — keys or values matching sensitive patterns (private keys, passwords, API keys, mnemonics, tokens, secrets) are stripped before being attached to spans.
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { sanitizeAttributes } from '@spectra-the-bot/cli-shared/telemetry';
|
|
286
|
+
|
|
287
|
+
const safe = sanitizeAttributes({
|
|
288
|
+
address: '0x1234', // ✓ kept
|
|
289
|
+
private_key: '0xdead', // ✗ stripped
|
|
290
|
+
api_key: 'sk-secret', // ✗ stripped
|
|
291
|
+
format: 'json', // ✓ kept
|
|
292
|
+
});
|
|
293
|
+
// => { address: '0x1234', format: 'json' }
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
214
298
|
## Export summary
|
|
215
299
|
|
|
216
300
|
- **Middleware**: `apiKeyAuth`, `MissingApiKeyError`, `withRetry`, `createRateLimiter`, `withRateLimit`, `paginateCursor`, `paginateOffset`
|
|
217
301
|
- **Utils**: `createHttpClient`, `HttpError`, `weiToEth`, `checksumAddress`, `formatTimestamp`, `truncate`
|
|
302
|
+
- **Telemetry**: `initTelemetry`, `shutdownTelemetry`, `createCommandSpan`, `withCommandSpan`, `withSpan`, `recordError`, `sanitizeAttributes`, `createHttpSpan`, `endHttpSpan`, `endHttpSpanWithError`, `extractPath`
|
|
218
303
|
- **Testing**: `createMockServer`
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createHttpSpan,
|
|
3
|
+
endHttpSpan,
|
|
4
|
+
endHttpSpanWithError
|
|
5
|
+
} from "./chunk-G7AGMLZE.js";
|
|
6
|
+
|
|
1
7
|
// src/utils/http.ts
|
|
8
|
+
import { context } from "@opentelemetry/api";
|
|
2
9
|
var HttpError = class extends Error {
|
|
3
10
|
constructor(status, statusText, body, headers = new Headers()) {
|
|
4
11
|
super(`HTTP ${status} ${statusText}: ${body}`);
|
|
@@ -35,16 +42,31 @@ function createHttpClient(options) {
|
|
|
35
42
|
init.body = JSON.stringify(body);
|
|
36
43
|
}
|
|
37
44
|
init.headers = mergedHeaders;
|
|
38
|
-
const
|
|
39
|
-
|
|
45
|
+
const { span, ctx } = createHttpSpan(method, url);
|
|
46
|
+
try {
|
|
47
|
+
const res = await context.with(ctx, () => fetch(url, init));
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const text2 = await res.text();
|
|
50
|
+
const err = new HttpError(res.status, res.statusText, text2, res.headers);
|
|
51
|
+
endHttpSpanWithError(span, err, res.status);
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
const contentLength = res.headers.get("content-length");
|
|
55
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
56
|
+
if (contentType.includes("application/json")) {
|
|
57
|
+
const data = await res.json();
|
|
58
|
+
endHttpSpan(span, res.status, contentLength ? Number(contentLength) : void 0);
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
40
61
|
const text = await res.text();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
endHttpSpan(span, res.status, contentLength ? Number(contentLength) : void 0);
|
|
63
|
+
return text;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (!(err instanceof HttpError)) {
|
|
66
|
+
endHttpSpanWithError(span, err);
|
|
67
|
+
}
|
|
68
|
+
throw err;
|
|
46
69
|
}
|
|
47
|
-
return res.text();
|
|
48
70
|
}
|
|
49
71
|
return { request };
|
|
50
72
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// src/telemetry/spans.ts
|
|
2
|
+
import { SpanKind, SpanStatusCode, context, trace } from "@opentelemetry/api";
|
|
3
|
+
import {
|
|
4
|
+
ATTR_HTTP_REQUEST_METHOD,
|
|
5
|
+
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
|
6
|
+
ATTR_URL_FULL,
|
|
7
|
+
ATTR_URL_PATH
|
|
8
|
+
} from "@opentelemetry/semantic-conventions";
|
|
9
|
+
var TRACER_NAME = "@spectratools/cli-shared";
|
|
10
|
+
var SENSITIVE_PATTERNS = [
|
|
11
|
+
/private[\s_-]?key/i,
|
|
12
|
+
/secret/i,
|
|
13
|
+
/password/i,
|
|
14
|
+
/passphrase/i,
|
|
15
|
+
/mnemonic/i,
|
|
16
|
+
/seed/i,
|
|
17
|
+
/token/i,
|
|
18
|
+
/api[\s_-]?key/i,
|
|
19
|
+
/auth/i
|
|
20
|
+
];
|
|
21
|
+
function sanitizeAttributes(attrs) {
|
|
22
|
+
const result = {};
|
|
23
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
24
|
+
const strKey = key.toLowerCase();
|
|
25
|
+
const strVal = typeof value === "string" ? value : "";
|
|
26
|
+
const isSensitive = SENSITIVE_PATTERNS.some(
|
|
27
|
+
(p) => p.test(strKey) || strVal.length > 0 && p.test(strVal)
|
|
28
|
+
);
|
|
29
|
+
if (isSensitive) continue;
|
|
30
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
31
|
+
result[key] = value;
|
|
32
|
+
} else if (value !== void 0 && value !== null) {
|
|
33
|
+
result[key] = String(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
function createCommandSpan(commandName, args) {
|
|
39
|
+
const tracer = trace.getTracer(TRACER_NAME);
|
|
40
|
+
const span = tracer.startSpan(`cli.command.${commandName}`);
|
|
41
|
+
if (args) {
|
|
42
|
+
const safe = sanitizeAttributes(args);
|
|
43
|
+
for (const [k, v] of Object.entries(safe)) {
|
|
44
|
+
span.setAttribute(`cli.arg.${k}`, v);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return span;
|
|
48
|
+
}
|
|
49
|
+
async function withCommandSpan(commandPath, args, fn) {
|
|
50
|
+
const span = createCommandSpan(commandPath, args);
|
|
51
|
+
try {
|
|
52
|
+
const result = await fn();
|
|
53
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
54
|
+
return result;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
recordError(span, err);
|
|
57
|
+
throw err;
|
|
58
|
+
} finally {
|
|
59
|
+
span.end();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function withSpan(name, fn) {
|
|
63
|
+
const tracer = trace.getTracer(TRACER_NAME);
|
|
64
|
+
const span = tracer.startSpan(name);
|
|
65
|
+
const ctx = trace.setSpan(context.active(), span);
|
|
66
|
+
try {
|
|
67
|
+
const result = await context.with(ctx, () => fn(span));
|
|
68
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
69
|
+
return result;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
recordError(span, err);
|
|
72
|
+
throw err;
|
|
73
|
+
} finally {
|
|
74
|
+
span.end();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function recordError(span, error) {
|
|
78
|
+
span.setStatus({
|
|
79
|
+
code: SpanStatusCode.ERROR,
|
|
80
|
+
message: error instanceof Error ? error.message : String(error)
|
|
81
|
+
});
|
|
82
|
+
if (error instanceof Error) {
|
|
83
|
+
span.recordException(error);
|
|
84
|
+
} else {
|
|
85
|
+
span.recordException(new Error(String(error)));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function extractPath(url) {
|
|
89
|
+
try {
|
|
90
|
+
return new URL(url).pathname;
|
|
91
|
+
} catch {
|
|
92
|
+
const qIndex = url.indexOf("?");
|
|
93
|
+
return qIndex >= 0 ? url.slice(0, qIndex) : url;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function createHttpSpan(method, url) {
|
|
97
|
+
const tracer = trace.getTracer(TRACER_NAME);
|
|
98
|
+
const path = extractPath(url);
|
|
99
|
+
const span = tracer.startSpan(`HTTP ${method} ${path}`, {
|
|
100
|
+
kind: SpanKind.CLIENT,
|
|
101
|
+
attributes: {
|
|
102
|
+
[ATTR_HTTP_REQUEST_METHOD]: method,
|
|
103
|
+
[ATTR_URL_FULL]: url,
|
|
104
|
+
[ATTR_URL_PATH]: path
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const ctx = trace.setSpan(context.active(), span);
|
|
108
|
+
return { span, ctx };
|
|
109
|
+
}
|
|
110
|
+
function endHttpSpan(span, statusCode, contentLength) {
|
|
111
|
+
span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, statusCode);
|
|
112
|
+
if (contentLength !== void 0) {
|
|
113
|
+
span.setAttribute("http.response_content_length", contentLength);
|
|
114
|
+
}
|
|
115
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
116
|
+
span.end();
|
|
117
|
+
}
|
|
118
|
+
function endHttpSpanWithError(span, error, statusCode) {
|
|
119
|
+
if (statusCode !== void 0) {
|
|
120
|
+
span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, statusCode);
|
|
121
|
+
}
|
|
122
|
+
recordError(span, error);
|
|
123
|
+
span.end();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export {
|
|
127
|
+
sanitizeAttributes,
|
|
128
|
+
createCommandSpan,
|
|
129
|
+
withCommandSpan,
|
|
130
|
+
withSpan,
|
|
131
|
+
recordError,
|
|
132
|
+
extractPath,
|
|
133
|
+
createHttpSpan,
|
|
134
|
+
endHttpSpan,
|
|
135
|
+
endHttpSpanWithError
|
|
136
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HttpError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BBONIM34.js";
|
|
4
|
+
import {
|
|
5
|
+
recordError
|
|
6
|
+
} from "./chunk-G7AGMLZE.js";
|
|
4
7
|
|
|
5
8
|
// src/middleware/auth.ts
|
|
6
9
|
var MissingApiKeyError = class extends Error {
|
|
@@ -43,6 +46,7 @@ async function* paginateOffset(options) {
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
// src/middleware/rate-limit.ts
|
|
49
|
+
import { trace } from "@opentelemetry/api";
|
|
46
50
|
function createRateLimiter(options) {
|
|
47
51
|
const { requestsPerSecond } = options;
|
|
48
52
|
const intervalMs = 1e3 / requestsPerSecond;
|
|
@@ -85,10 +89,21 @@ function createRateLimiter(options) {
|
|
|
85
89
|
};
|
|
86
90
|
}
|
|
87
91
|
function withRateLimit(fn, acquire) {
|
|
88
|
-
|
|
92
|
+
const start = Date.now();
|
|
93
|
+
return acquire().then(() => {
|
|
94
|
+
const waitMs = Date.now() - start;
|
|
95
|
+
if (waitMs > 0) {
|
|
96
|
+
const activeSpan = trace.getActiveSpan();
|
|
97
|
+
if (activeSpan) {
|
|
98
|
+
activeSpan.setAttribute("rate_limit.wait_ms", waitMs);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return fn();
|
|
102
|
+
});
|
|
89
103
|
}
|
|
90
104
|
|
|
91
105
|
// src/middleware/retry.ts
|
|
106
|
+
import { trace as trace2 } from "@opentelemetry/api";
|
|
92
107
|
function sleep(ms) {
|
|
93
108
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
94
109
|
}
|
|
@@ -111,7 +126,13 @@ async function withRetry(fn, options) {
|
|
|
111
126
|
try {
|
|
112
127
|
return await fn();
|
|
113
128
|
} catch (err) {
|
|
114
|
-
if (attempt >= maxRetries)
|
|
129
|
+
if (attempt >= maxRetries) {
|
|
130
|
+
const activeSpan2 = trace2.getActiveSpan();
|
|
131
|
+
if (activeSpan2) {
|
|
132
|
+
recordError(activeSpan2, err);
|
|
133
|
+
}
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
115
136
|
let delayMs;
|
|
116
137
|
if (err instanceof HttpError && (err.status === 429 || err.status === 503)) {
|
|
117
138
|
const retryAfterMs = parseRetryAfter(err.headers);
|
|
@@ -119,8 +140,18 @@ async function withRetry(fn, options) {
|
|
|
119
140
|
} else {
|
|
120
141
|
delayMs = Math.min(baseMs * 2 ** attempt, maxMs);
|
|
121
142
|
}
|
|
122
|
-
|
|
143
|
+
const actualDelay = jitter(delayMs);
|
|
123
144
|
attempt++;
|
|
145
|
+
const activeSpan = trace2.getActiveSpan();
|
|
146
|
+
if (activeSpan) {
|
|
147
|
+
activeSpan.setAttribute("retry.attempt", attempt);
|
|
148
|
+
activeSpan.addEvent("retry", {
|
|
149
|
+
attempt,
|
|
150
|
+
delay_ms: Math.round(actualDelay),
|
|
151
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
await sleep(actualDelay);
|
|
124
155
|
}
|
|
125
156
|
}
|
|
126
157
|
}
|
|
@@ -8,7 +8,7 @@ var require_package = __commonJS({
|
|
|
8
8
|
"package.json"(exports, module) {
|
|
9
9
|
module.exports = {
|
|
10
10
|
name: "@spectratools/cli-shared",
|
|
11
|
-
version: "0.
|
|
11
|
+
version: "0.4.0",
|
|
12
12
|
description: "Shared middleware, utilities, and testing helpers for spectra CLI tools",
|
|
13
13
|
type: "module",
|
|
14
14
|
license: "MIT",
|
|
@@ -116,75 +116,6 @@ function _getSdk() {
|
|
|
116
116
|
return sdk;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
// src/telemetry/spans.ts
|
|
120
|
-
import { SpanStatusCode, context, trace } from "@opentelemetry/api";
|
|
121
|
-
var TRACER_NAME = "@spectratools/cli-shared";
|
|
122
|
-
var SENSITIVE_PATTERNS = [
|
|
123
|
-
/private[\s_-]?key/i,
|
|
124
|
-
/secret/i,
|
|
125
|
-
/password/i,
|
|
126
|
-
/passphrase/i,
|
|
127
|
-
/mnemonic/i,
|
|
128
|
-
/seed/i,
|
|
129
|
-
/token/i,
|
|
130
|
-
/api[\s_-]?key/i,
|
|
131
|
-
/auth/i
|
|
132
|
-
];
|
|
133
|
-
function sanitizeAttributes(attrs) {
|
|
134
|
-
const result = {};
|
|
135
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
136
|
-
const strKey = key.toLowerCase();
|
|
137
|
-
const strVal = typeof value === "string" ? value : "";
|
|
138
|
-
const isSensitive = SENSITIVE_PATTERNS.some(
|
|
139
|
-
(p) => p.test(strKey) || strVal.length > 0 && p.test(strVal)
|
|
140
|
-
);
|
|
141
|
-
if (isSensitive) continue;
|
|
142
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
143
|
-
result[key] = value;
|
|
144
|
-
} else if (value !== void 0 && value !== null) {
|
|
145
|
-
result[key] = String(value);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return result;
|
|
149
|
-
}
|
|
150
|
-
function createCommandSpan(commandName, args) {
|
|
151
|
-
const tracer = trace.getTracer(TRACER_NAME);
|
|
152
|
-
const span = tracer.startSpan(`cli.command.${commandName}`);
|
|
153
|
-
if (args) {
|
|
154
|
-
const safe = sanitizeAttributes(args);
|
|
155
|
-
for (const [k, v] of Object.entries(safe)) {
|
|
156
|
-
span.setAttribute(`cli.arg.${k}`, v);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return span;
|
|
160
|
-
}
|
|
161
|
-
async function withSpan(name, fn) {
|
|
162
|
-
const tracer = trace.getTracer(TRACER_NAME);
|
|
163
|
-
const span = tracer.startSpan(name);
|
|
164
|
-
const ctx = trace.setSpan(context.active(), span);
|
|
165
|
-
try {
|
|
166
|
-
const result = await context.with(ctx, () => fn(span));
|
|
167
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
168
|
-
return result;
|
|
169
|
-
} catch (err) {
|
|
170
|
-
recordError(span, err);
|
|
171
|
-
throw err;
|
|
172
|
-
} finally {
|
|
173
|
-
span.end();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
function recordError(span, error) {
|
|
177
|
-
span.setStatus({
|
|
178
|
-
code: SpanStatusCode.ERROR,
|
|
179
|
-
message: error instanceof Error ? error.message : String(error)
|
|
180
|
-
});
|
|
181
|
-
if (error instanceof Error) {
|
|
182
|
-
span.recordException(error);
|
|
183
|
-
} else {
|
|
184
|
-
span.recordException(new Error(String(error)));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
119
|
// src/telemetry/shutdown.ts
|
|
189
120
|
async function shutdownTelemetry() {
|
|
190
121
|
const sdk2 = _getSdk();
|
|
@@ -194,8 +125,5 @@ async function shutdownTelemetry() {
|
|
|
194
125
|
|
|
195
126
|
export {
|
|
196
127
|
initTelemetry,
|
|
197
|
-
createCommandSpan,
|
|
198
|
-
withSpan,
|
|
199
|
-
recordError,
|
|
200
128
|
shutdownTelemetry
|
|
201
129
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ApiKeyAuthContext, CursorPaginationOptions, MissingApiKeyError, OffsetPaginationOptions, RateLimitOptions, RetryOptions, apiKeyAuth, createRateLimiter, paginateCursor, paginateOffset, withRateLimit, withRetry } from './middleware/index.js';
|
|
2
|
-
export { createCommandSpan, initTelemetry, recordError, shutdownTelemetry, withSpan } from './telemetry/index.js';
|
|
2
|
+
export { createCommandSpan, createHttpSpan, endHttpSpan, endHttpSpanWithError, extractPath, initTelemetry, recordError, sanitizeAttributes, shutdownTelemetry, withCommandSpan, withSpan } from './telemetry/index.js';
|
|
3
3
|
export { MockResponse, MockServer, RecordedRequest, createMockServer } from './testing/index.js';
|
|
4
4
|
export { HttpClientOptions, HttpError, RequestOptions, checksumAddress, createHttpClient, formatTimestamp, isAddress, truncate, weiToEth } from './utils/index.js';
|
|
5
5
|
import '@opentelemetry/api';
|
package/dist/index.js
CHANGED
|
@@ -6,14 +6,11 @@ import {
|
|
|
6
6
|
paginateOffset,
|
|
7
7
|
withRateLimit,
|
|
8
8
|
withRetry
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-I2D7COI7.js";
|
|
10
10
|
import {
|
|
11
|
-
createCommandSpan,
|
|
12
11
|
initTelemetry,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
withSpan
|
|
16
|
-
} from "./chunk-YWGC33UJ.js";
|
|
12
|
+
shutdownTelemetry
|
|
13
|
+
} from "./chunk-S4AQMOWL.js";
|
|
17
14
|
import {
|
|
18
15
|
createMockServer
|
|
19
16
|
} from "./chunk-QEANTXUE.js";
|
|
@@ -27,7 +24,18 @@ import {
|
|
|
27
24
|
import {
|
|
28
25
|
HttpError,
|
|
29
26
|
createHttpClient
|
|
30
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-BBONIM34.js";
|
|
28
|
+
import {
|
|
29
|
+
createCommandSpan,
|
|
30
|
+
createHttpSpan,
|
|
31
|
+
endHttpSpan,
|
|
32
|
+
endHttpSpanWithError,
|
|
33
|
+
extractPath,
|
|
34
|
+
recordError,
|
|
35
|
+
sanitizeAttributes,
|
|
36
|
+
withCommandSpan,
|
|
37
|
+
withSpan
|
|
38
|
+
} from "./chunk-G7AGMLZE.js";
|
|
31
39
|
import "./chunk-MCKGQKYU.js";
|
|
32
40
|
export {
|
|
33
41
|
HttpError,
|
|
@@ -36,17 +44,23 @@ export {
|
|
|
36
44
|
checksumAddress,
|
|
37
45
|
createCommandSpan,
|
|
38
46
|
createHttpClient,
|
|
47
|
+
createHttpSpan,
|
|
39
48
|
createMockServer,
|
|
40
49
|
createRateLimiter,
|
|
50
|
+
endHttpSpan,
|
|
51
|
+
endHttpSpanWithError,
|
|
52
|
+
extractPath,
|
|
41
53
|
formatTimestamp,
|
|
42
54
|
initTelemetry,
|
|
43
55
|
isAddress,
|
|
44
56
|
paginateCursor,
|
|
45
57
|
paginateOffset,
|
|
46
58
|
recordError,
|
|
59
|
+
sanitizeAttributes,
|
|
47
60
|
shutdownTelemetry,
|
|
48
61
|
truncate,
|
|
49
62
|
weiToEth,
|
|
63
|
+
withCommandSpan,
|
|
50
64
|
withRateLimit,
|
|
51
65
|
withRetry,
|
|
52
66
|
withSpan
|
|
@@ -42,6 +42,7 @@ interface RateLimitOptions {
|
|
|
42
42
|
declare function createRateLimiter(options: RateLimitOptions): () => Promise<void>;
|
|
43
43
|
/**
|
|
44
44
|
* Wraps a fetch-like function with token bucket rate limiting.
|
|
45
|
+
* Records wait time as a span attribute when OTEL is active.
|
|
45
46
|
*/
|
|
46
47
|
declare function withRateLimit<T>(fn: () => Promise<T>, acquire: () => Promise<void>): Promise<T>;
|
|
47
48
|
|
|
@@ -53,6 +54,7 @@ interface RetryOptions {
|
|
|
53
54
|
/**
|
|
54
55
|
* Wraps a fetch-like function with exponential backoff retry logic.
|
|
55
56
|
* Respects Retry-After headers on 429/503 responses.
|
|
57
|
+
* Records retry attempts as span events and attributes when OTEL is active.
|
|
56
58
|
*/
|
|
57
59
|
declare function withRetry<T>(fn: () => Promise<T>, options: RetryOptions): Promise<T>;
|
|
58
60
|
|
package/dist/middleware/index.js
CHANGED
|
@@ -6,8 +6,9 @@ import {
|
|
|
6
6
|
paginateOffset,
|
|
7
7
|
withRateLimit,
|
|
8
8
|
withRetry
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-I2D7COI7.js";
|
|
10
|
+
import "../chunk-BBONIM34.js";
|
|
11
|
+
import "../chunk-G7AGMLZE.js";
|
|
11
12
|
import "../chunk-MCKGQKYU.js";
|
|
12
13
|
export {
|
|
13
14
|
MissingApiKeyError,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Span } from '@opentelemetry/api';
|
|
1
|
+
import { Span, trace } from '@opentelemetry/api';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Initialize OpenTelemetry SDK for a CLI service.
|
|
@@ -11,6 +11,11 @@ import { Span } from '@opentelemetry/api';
|
|
|
11
11
|
*/
|
|
12
12
|
declare function initTelemetry(serviceName: string): void;
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Sanitize a record of span attributes by stripping any key/value that looks
|
|
16
|
+
* like it contains a private key, password, or other sensitive material.
|
|
17
|
+
*/
|
|
18
|
+
declare function sanitizeAttributes(attrs: Record<string, unknown>): Record<string, string | number | boolean>;
|
|
14
19
|
/**
|
|
15
20
|
* Create a root span for a CLI command invocation.
|
|
16
21
|
*
|
|
@@ -19,6 +24,17 @@ declare function initTelemetry(serviceName: string): void;
|
|
|
19
24
|
* @returns A started `Span`. The caller is responsible for calling `span.end()`.
|
|
20
25
|
*/
|
|
21
26
|
declare function createCommandSpan(commandName: string, args?: Record<string, unknown>): Span;
|
|
27
|
+
/**
|
|
28
|
+
* Convenience wrapper that creates a root command span, executes `fn`, and
|
|
29
|
+
* automatically sets the span status and ends it. On error, the error is
|
|
30
|
+
* recorded on the span and re-thrown.
|
|
31
|
+
*
|
|
32
|
+
* @param commandPath - The command being executed (e.g. `"account balance"`).
|
|
33
|
+
* @param args - Key-value arguments to attach as sanitized span attributes.
|
|
34
|
+
* @param fn - Async function to execute within the span.
|
|
35
|
+
* @returns The result of `fn`.
|
|
36
|
+
*/
|
|
37
|
+
declare function withCommandSpan<T>(commandPath: string, args: Record<string, unknown>, fn: () => Promise<T>): Promise<T>;
|
|
22
38
|
/**
|
|
23
39
|
* Convenience wrapper that creates a child span, runs the given async function,
|
|
24
40
|
* and automatically ends the span. On error, the error is recorded on the span
|
|
@@ -36,6 +52,29 @@ declare function withSpan<T>(name: string, fn: (span: Span) => Promise<T>): Prom
|
|
|
36
52
|
* @param error - The error value (may be an `Error` instance or arbitrary value).
|
|
37
53
|
*/
|
|
38
54
|
declare function recordError(span: Span, error: unknown): void;
|
|
55
|
+
/**
|
|
56
|
+
* Extract the pathname from a URL string, stripping the base URL and query parameters.
|
|
57
|
+
*/
|
|
58
|
+
declare function extractPath(url: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Create a child span for an HTTP client request.
|
|
61
|
+
*
|
|
62
|
+
* @param method - HTTP method (e.g. GET, POST).
|
|
63
|
+
* @param url - Full request URL.
|
|
64
|
+
* @returns An object with the started `span` and `ctx` for context propagation.
|
|
65
|
+
*/
|
|
66
|
+
declare function createHttpSpan(method: string, url: string): {
|
|
67
|
+
span: Span;
|
|
68
|
+
ctx: ReturnType<typeof trace.setSpan>;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Finalize an HTTP span with response details.
|
|
72
|
+
*/
|
|
73
|
+
declare function endHttpSpan(span: Span, statusCode: number, contentLength?: number): void;
|
|
74
|
+
/**
|
|
75
|
+
* Finalize an HTTP span with an error.
|
|
76
|
+
*/
|
|
77
|
+
declare function endHttpSpanWithError(span: Span, error: unknown, statusCode?: number): void;
|
|
39
78
|
|
|
40
79
|
/**
|
|
41
80
|
* Flush pending spans/metrics and shut down the OTEL SDK.
|
|
@@ -44,4 +83,4 @@ declare function recordError(span: Span, error: unknown): void;
|
|
|
44
83
|
*/
|
|
45
84
|
declare function shutdownTelemetry(): Promise<void>;
|
|
46
85
|
|
|
47
|
-
export { createCommandSpan, initTelemetry, recordError, shutdownTelemetry, withSpan };
|
|
86
|
+
export { createCommandSpan, createHttpSpan, endHttpSpan, endHttpSpanWithError, extractPath, initTelemetry, recordError, sanitizeAttributes, shutdownTelemetry, withCommandSpan, withSpan };
|
package/dist/telemetry/index.js
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createCommandSpan,
|
|
3
2
|
initTelemetry,
|
|
3
|
+
shutdownTelemetry
|
|
4
|
+
} from "../chunk-S4AQMOWL.js";
|
|
5
|
+
import {
|
|
6
|
+
createCommandSpan,
|
|
7
|
+
createHttpSpan,
|
|
8
|
+
endHttpSpan,
|
|
9
|
+
endHttpSpanWithError,
|
|
10
|
+
extractPath,
|
|
4
11
|
recordError,
|
|
5
|
-
|
|
12
|
+
sanitizeAttributes,
|
|
13
|
+
withCommandSpan,
|
|
6
14
|
withSpan
|
|
7
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-G7AGMLZE.js";
|
|
8
16
|
import "../chunk-MCKGQKYU.js";
|
|
9
17
|
export {
|
|
10
18
|
createCommandSpan,
|
|
19
|
+
createHttpSpan,
|
|
20
|
+
endHttpSpan,
|
|
21
|
+
endHttpSpanWithError,
|
|
22
|
+
extractPath,
|
|
11
23
|
initTelemetry,
|
|
12
24
|
recordError,
|
|
25
|
+
sanitizeAttributes,
|
|
13
26
|
shutdownTelemetry,
|
|
27
|
+
withCommandSpan,
|
|
14
28
|
withSpan
|
|
15
29
|
};
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ declare class HttpError extends Error {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Typed fetch wrapper with base URL, default headers, query serialization, and error handling.
|
|
42
|
+
* Automatically creates OpenTelemetry spans for each request when OTEL is active.
|
|
42
43
|
*/
|
|
43
44
|
declare function createHttpClient(options: HttpClientOptions): {
|
|
44
45
|
request: <T>(path: string, opts?: RequestOptions) => Promise<T>;
|
package/dist/utils/index.js
CHANGED