@getargvio/argvio 0.1.0 → 0.5.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 +32 -6
- package/dist/index.cjs +529 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +249 -48
- package/dist/index.d.ts +249 -48
- package/dist/index.js +500 -196
- package/dist/index.js.map +1 -1
- package/package.json +8 -9
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
1
2
|
import { SeverityNumber } from '@opentelemetry/api-logs';
|
|
2
3
|
import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
|
|
3
4
|
import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
4
5
|
import { NodeTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { readFileSync, mkdirSync, writeFileSync, statSync } from 'fs';
|
|
5
9
|
import { Metadata } from '@grpc/grpc-js';
|
|
6
10
|
import { OTLPTraceExporter as OTLPTraceExporter$1 } from '@opentelemetry/exporter-trace-otlp-http';
|
|
7
11
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
|
|
@@ -9,22 +13,22 @@ import { OTLPMetricExporter as OTLPMetricExporter$1 } from '@opentelemetry/expor
|
|
|
9
13
|
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
|
|
10
14
|
import { OTLPLogExporter as OTLPLogExporter$1 } from '@opentelemetry/exporter-logs-otlp-http';
|
|
11
15
|
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
|
|
12
|
-
import { readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
13
16
|
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
14
17
|
import { ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
|
15
|
-
import { randomBytes } from 'crypto';
|
|
16
|
-
import { homedir } from 'os';
|
|
17
|
-
import { dirname, join } from 'path';
|
|
18
|
+
import { randomBytes, randomUUID } from 'crypto';
|
|
18
19
|
|
|
19
20
|
// src/client.ts
|
|
20
21
|
|
|
21
22
|
// src/tier.ts
|
|
22
23
|
var Tier = {
|
|
23
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* No persistent or cross-invocation identifiers: command name, exit
|
|
26
|
+
* code, duration, CLI version, OS/arch and CI environment only.
|
|
27
|
+
*/
|
|
24
28
|
Anonymous: 0,
|
|
25
|
-
/** Adds
|
|
29
|
+
/** Adds an opaque per-process session ID, an opaque per-install ID, flag names (never values), and classified (not raw) error types. */
|
|
26
30
|
Basic: 1,
|
|
27
|
-
/**
|
|
31
|
+
/** Reserved: the server allowlist permits some fields here, but this SDK sends nothing beyond `Tier.Basic`. */
|
|
28
32
|
Full: 2,
|
|
29
33
|
/**
|
|
30
34
|
* The explicit opt-in tier: raw error detail (stack traces, messages)
|
|
@@ -38,7 +42,7 @@ var TIER_STRINGS = {
|
|
|
38
42
|
[Tier.Anonymous]: "anonymous",
|
|
39
43
|
[Tier.Basic]: "basic",
|
|
40
44
|
[Tier.Full]: "full",
|
|
41
|
-
[Tier.OptIn]: "
|
|
45
|
+
[Tier.OptIn]: "optin_plus"
|
|
42
46
|
};
|
|
43
47
|
function tierToString(tier) {
|
|
44
48
|
return TIER_STRINGS[tier] ?? "unknown";
|
|
@@ -51,6 +55,7 @@ function parseTier(value) {
|
|
|
51
55
|
return Tier.Basic;
|
|
52
56
|
case "full":
|
|
53
57
|
return Tier.Full;
|
|
58
|
+
case "optin_plus":
|
|
54
59
|
case "opt-in":
|
|
55
60
|
return Tier.OptIn;
|
|
56
61
|
default:
|
|
@@ -82,6 +87,72 @@ function isValidErrorCategory(value) {
|
|
|
82
87
|
return ERROR_CATEGORY_VALUES.has(value);
|
|
83
88
|
}
|
|
84
89
|
|
|
90
|
+
// src/errors.ts
|
|
91
|
+
var ArgvioError = class extends Error {
|
|
92
|
+
constructor(message) {
|
|
93
|
+
super(`argvio: ${message}`);
|
|
94
|
+
this.name = "ArgvioError";
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var errNoProviderResolved = new ArgvioError("no consent provider resolved a tier");
|
|
98
|
+
var errNoConsentFile = new ArgvioError("no consent config file path resolved");
|
|
99
|
+
var errUnknownTierValue = new ArgvioError("unknown tier value in consent config");
|
|
100
|
+
|
|
101
|
+
// src/consentFile.ts
|
|
102
|
+
function userConfigDir() {
|
|
103
|
+
switch (process.platform) {
|
|
104
|
+
case "win32":
|
|
105
|
+
return process.env["APPDATA"];
|
|
106
|
+
case "darwin":
|
|
107
|
+
return join(homedir(), "Library", "Application Support");
|
|
108
|
+
default:
|
|
109
|
+
return process.env["XDG_CONFIG_HOME"] || join(homedir(), ".config");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function defaultInstallIdPath(cliName) {
|
|
113
|
+
const consentPath = defaultConsentPath(cliName);
|
|
114
|
+
return consentPath ? join(dirname(consentPath), "argvio-install-id") : void 0;
|
|
115
|
+
}
|
|
116
|
+
function defaultConsentPath(cliName) {
|
|
117
|
+
const dir = userConfigDir();
|
|
118
|
+
if (!dir) return void 0;
|
|
119
|
+
return join(dir, cliName || "argvio", "argvio-consent.json");
|
|
120
|
+
}
|
|
121
|
+
var FileConsentProvider = class {
|
|
122
|
+
/** The resolved config file path (after defaults/overrides), primarily for diagnostics and tests. */
|
|
123
|
+
path;
|
|
124
|
+
constructor(cliName, options = {}) {
|
|
125
|
+
this.path = options.path || defaultConsentPath(cliName);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A missing file, unreadable file, or malformed contents all throw
|
|
129
|
+
* synchronously, so callers should place `FileConsentProvider` inside
|
|
130
|
+
* `chainProviders` with a sane fallback (e.g.
|
|
131
|
+
* `new StaticProvider(Tier.Anonymous)`).
|
|
132
|
+
*/
|
|
133
|
+
resolve() {
|
|
134
|
+
if (!this.path) throw errNoConsentFile;
|
|
135
|
+
const data = readFileSync(this.path, "utf8");
|
|
136
|
+
const parsed = JSON.parse(data);
|
|
137
|
+
const tier = parseTier(parsed.tier ?? "");
|
|
138
|
+
if (tier === void 0) throw errUnknownTierValue;
|
|
139
|
+
return tier;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Persists `tier` as the chosen consent tier, creating the parent
|
|
143
|
+
* directory if needed. Intended for use by a vendor's first-run
|
|
144
|
+
* consent prompt. Does not itself enforce any tier ceiling — it's the
|
|
145
|
+
* vendor's own UX writing the user's explicit choice.
|
|
146
|
+
*/
|
|
147
|
+
store(tier) {
|
|
148
|
+
if (!this.path) throw errNoConsentFile;
|
|
149
|
+
if (!isValidTier(tier)) throw errUnknownTierValue;
|
|
150
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 448 });
|
|
151
|
+
const contents = { tier: tierToString(tier) };
|
|
152
|
+
writeFileSync(this.path, JSON.stringify(contents), { mode: 384 });
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
85
156
|
// src/options.ts
|
|
86
157
|
var DEFAULT_ENDPOINT_HTTP = "https://ingest.argvio.io:4318";
|
|
87
158
|
var DEFAULT_ENDPOINT_GRPC = "ingest.argvio.io:4317";
|
|
@@ -89,6 +160,7 @@ var DEFAULT_AUTH_HEADER = "x-argvio-api-key";
|
|
|
89
160
|
var DEFAULT_EXPORT_TIMEOUT_MS = 1e4;
|
|
90
161
|
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
91
162
|
var DEFAULT_QUEUE_SIZE = 2048;
|
|
163
|
+
var MAX_INSTALL_METHOD_LENGTH = 32;
|
|
92
164
|
function resolveConfig(apiKey, cliName, cliVersion, options = {}) {
|
|
93
165
|
const transport = options.transport ?? "http";
|
|
94
166
|
return {
|
|
@@ -105,7 +177,9 @@ function resolveConfig(apiKey, cliName, cliVersion, options = {}) {
|
|
|
105
177
|
exportTimeoutMs: options.exportTimeoutMs && options.exportTimeoutMs > 0 ? options.exportTimeoutMs : DEFAULT_EXPORT_TIMEOUT_MS,
|
|
106
178
|
shutdownTimeoutMs: options.shutdownTimeoutMs && options.shutdownTimeoutMs > 0 ? options.shutdownTimeoutMs : DEFAULT_SHUTDOWN_TIMEOUT_MS,
|
|
107
179
|
queueSize: options.queueSize && options.queueSize > 0 ? options.queueSize : DEFAULT_QUEUE_SIZE,
|
|
108
|
-
resourceAttributes: options.resourceAttributes ?? {}
|
|
180
|
+
resourceAttributes: options.resourceAttributes ?? {},
|
|
181
|
+
installIdPath: options.installIdPath !== void 0 ? options.installIdPath || void 0 : defaultInstallIdPath(cliName),
|
|
182
|
+
installMethod: options.installMethod?.slice(0, MAX_INSTALL_METHOD_LENGTH) || void 0
|
|
109
183
|
};
|
|
110
184
|
}
|
|
111
185
|
var SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
@@ -153,37 +227,6 @@ function buildExporters(cfg) {
|
|
|
153
227
|
})
|
|
154
228
|
};
|
|
155
229
|
}
|
|
156
|
-
var SDK_NAME = "@getargvio/argvio";
|
|
157
|
-
function sdkVersion() {
|
|
158
|
-
try {
|
|
159
|
-
const pkgUrl = new URL("../package.json", import.meta.url);
|
|
160
|
-
const pkg = JSON.parse(readFileSync(pkgUrl, "utf8"));
|
|
161
|
-
return pkg.version ?? "unknown";
|
|
162
|
-
} catch {
|
|
163
|
-
return "unknown";
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
function buildResource(cfg) {
|
|
167
|
-
return resourceFromAttributes({
|
|
168
|
-
[ATTR_SERVICE_NAME]: cfg.cliName,
|
|
169
|
-
[ATTR_SERVICE_VERSION]: cfg.cliVersion,
|
|
170
|
-
"telemetry.sdk.name": SDK_NAME,
|
|
171
|
-
"telemetry.sdk.version": sdkVersion(),
|
|
172
|
-
"telemetry.sdk.language": "nodejs",
|
|
173
|
-
...cfg.resourceAttributes
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// src/errors.ts
|
|
178
|
-
var ArgvioError = class extends Error {
|
|
179
|
-
constructor(message) {
|
|
180
|
-
super(`argvio: ${message}`);
|
|
181
|
-
this.name = "ArgvioError";
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
var errNoProviderResolved = new ArgvioError("no consent provider resolved a tier");
|
|
185
|
-
var errNoConsentFile = new ArgvioError("no consent config file path resolved");
|
|
186
|
-
var errUnknownTierValue = new ArgvioError("unknown tier value in consent config");
|
|
187
230
|
|
|
188
231
|
// src/consentEnv.ts
|
|
189
232
|
var CI_ENV_VARS = [
|
|
@@ -210,6 +253,21 @@ function isCI() {
|
|
|
210
253
|
return value !== void 0 && value !== "" && value !== "false" && value !== "0";
|
|
211
254
|
});
|
|
212
255
|
}
|
|
256
|
+
var CI_PROVIDERS = [
|
|
257
|
+
["GITHUB_ACTIONS", "github_actions"],
|
|
258
|
+
["GITLAB_CI", "gitlab_ci"],
|
|
259
|
+
["CIRCLECI", "circleci"],
|
|
260
|
+
["BUILDKITE", "buildkite"],
|
|
261
|
+
["JENKINS_URL", "jenkins"],
|
|
262
|
+
["TRAVIS", "travis"]
|
|
263
|
+
];
|
|
264
|
+
function detectCIProvider() {
|
|
265
|
+
for (const [envVar, provider] of CI_PROVIDERS) {
|
|
266
|
+
const value = process.env[envVar];
|
|
267
|
+
if (value && value !== "false" && value !== "0") return provider;
|
|
268
|
+
}
|
|
269
|
+
return isCI() ? "unknown" : void 0;
|
|
270
|
+
}
|
|
213
271
|
function isDoNotTrackRequested() {
|
|
214
272
|
const value = process.env["DO_NOT_TRACK"];
|
|
215
273
|
if (value === void 0) return false;
|
|
@@ -221,15 +279,139 @@ var EnvConsentProvider = class {
|
|
|
221
279
|
throw errNoProviderResolved;
|
|
222
280
|
}
|
|
223
281
|
};
|
|
224
|
-
|
|
225
|
-
|
|
282
|
+
|
|
283
|
+
// src/resource.ts
|
|
284
|
+
var ResourceAttrCLIVersion = "cli.version";
|
|
285
|
+
var ResourceAttrOS = "cli.os";
|
|
286
|
+
var ResourceAttrArch = "cli.arch";
|
|
287
|
+
var ResourceAttrIsCI = "cli.is_ci";
|
|
288
|
+
var ResourceAttrCIProvider = "cli.ci_provider";
|
|
289
|
+
function normalizeOS(platform) {
|
|
290
|
+
switch (platform) {
|
|
291
|
+
case "linux":
|
|
292
|
+
case "darwin":
|
|
293
|
+
return platform;
|
|
294
|
+
case "win32":
|
|
295
|
+
return "windows";
|
|
296
|
+
default:
|
|
297
|
+
return "other";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function normalizeArch(arch) {
|
|
301
|
+
switch (arch) {
|
|
302
|
+
case "x64":
|
|
303
|
+
return "amd64";
|
|
304
|
+
case "arm64":
|
|
305
|
+
case "arm":
|
|
306
|
+
return arch;
|
|
307
|
+
case "ia32":
|
|
308
|
+
return "386";
|
|
309
|
+
default:
|
|
310
|
+
return "other";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
var SDK_NAME = "@getargvio/argvio";
|
|
314
|
+
function sdkVersion() {
|
|
315
|
+
try {
|
|
316
|
+
const pkgUrl = new URL("../package.json", import.meta.url);
|
|
317
|
+
const pkg = JSON.parse(readFileSync(pkgUrl, "utf8"));
|
|
318
|
+
return pkg.version ?? "unknown";
|
|
319
|
+
} catch {
|
|
320
|
+
return "unknown";
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function buildResource(cfg) {
|
|
324
|
+
const ciProvider = detectCIProvider();
|
|
325
|
+
return resourceFromAttributes({
|
|
326
|
+
[ATTR_SERVICE_NAME]: cfg.cliName,
|
|
327
|
+
[ATTR_SERVICE_VERSION]: cfg.cliVersion,
|
|
328
|
+
"telemetry.sdk.name": SDK_NAME,
|
|
329
|
+
"telemetry.sdk.version": sdkVersion(),
|
|
330
|
+
"telemetry.sdk.language": "nodejs",
|
|
331
|
+
[ResourceAttrCLIVersion]: cfg.cliVersion,
|
|
332
|
+
[ResourceAttrOS]: normalizeOS(process.platform),
|
|
333
|
+
[ResourceAttrArch]: normalizeArch(process.arch),
|
|
334
|
+
[ResourceAttrIsCI]: ciProvider !== void 0,
|
|
335
|
+
...ciProvider ? { [ResourceAttrCIProvider]: ciProvider } : {},
|
|
336
|
+
...cfg.resourceAttributes
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
var AttrCommandName = "cli.command.name";
|
|
226
340
|
var AttrExitCode = "cli.command.exit_code";
|
|
227
|
-
var
|
|
228
|
-
var
|
|
229
|
-
var
|
|
341
|
+
var AttrDurationMs = "cli.command.duration_ms";
|
|
342
|
+
var AttrPeakRssBytes = "cli.process.peak_rss_bytes";
|
|
343
|
+
var AttrCpuTimeMs = "cli.process.cpu_time_ms";
|
|
344
|
+
var AttrBinarySizeBytes = "cli.binary.size_bytes";
|
|
345
|
+
var AttrStartupDurationMs = "cli.startup.duration_ms";
|
|
346
|
+
var AttrFlagNames = "cli.flag.names";
|
|
347
|
+
var AttrSessionId = "cli.session_id";
|
|
348
|
+
var AttrInstallId = "cli.install_id";
|
|
349
|
+
var AttrHelpFlag = "cli.help.flag";
|
|
350
|
+
var AttrInstallMethod = "cli.install.method";
|
|
351
|
+
var AttrActivationFeature = "cli.activation.feature";
|
|
352
|
+
var AttrErrorType = "cli.error.type";
|
|
353
|
+
var AttrErrorReason = "cli.error.reason";
|
|
230
354
|
var AttrErrorMessage = "cli.error.message";
|
|
231
|
-
var AttrErrorStack = "cli.error.
|
|
355
|
+
var AttrErrorStack = "cli.error.stacktrace";
|
|
356
|
+
var AttrCounterName = "cli.counter.name";
|
|
357
|
+
var AttrCounterLabel = "cli.counter.label";
|
|
358
|
+
var EventSessionStarted = "cli.session.started";
|
|
359
|
+
var EventCommandInvoked = "cli.command.invoked";
|
|
360
|
+
var EventCommandCompleted = "cli.command.completed";
|
|
361
|
+
var EventFeatureActivated = "cli.feature.activated";
|
|
362
|
+
var EventHelpViewed = "cli.help.viewed";
|
|
363
|
+
var EventErrorRaised = "cli.error.raised";
|
|
364
|
+
var SpanCommandPrefix = "cli.command.";
|
|
365
|
+
var MetricCommandInvocations = "cli.command.invocations";
|
|
232
366
|
var MetricCommandDuration = "cli.command.duration";
|
|
367
|
+
var MetricErrors = "cli.errors";
|
|
368
|
+
var MetricCounter = "cli.counter";
|
|
369
|
+
var MAX_COMMAND_NAME_LENGTH = 128;
|
|
370
|
+
var MAX_ACTIVATION_FEATURE_LENGTH = 64;
|
|
371
|
+
var MAX_IDENTIFIER_LENGTH = 64;
|
|
372
|
+
var IDENTIFIER_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
373
|
+
function isValidIdentifier(value) {
|
|
374
|
+
return typeof value === "string" && value.length <= MAX_IDENTIFIER_LENGTH && IDENTIFIER_PATTERN.test(value);
|
|
375
|
+
}
|
|
376
|
+
function commandName(cliName, commandPath) {
|
|
377
|
+
let parts = commandPath.split(/\s+/).filter(Boolean);
|
|
378
|
+
if (parts.length > 1 && parts[0] === cliName) parts = parts.slice(1);
|
|
379
|
+
const name = parts.join(".") || cliName || "root";
|
|
380
|
+
return name.slice(0, MAX_COMMAND_NAME_LENGTH);
|
|
381
|
+
}
|
|
382
|
+
function readProcessResourceUsage() {
|
|
383
|
+
const usage = {};
|
|
384
|
+
try {
|
|
385
|
+
const rssBytes = Math.round(process.resourceUsage().maxRSS * 1024);
|
|
386
|
+
if (Number.isFinite(rssBytes) && rssBytes > 0) usage.peakRssBytes = rssBytes;
|
|
387
|
+
} catch {
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
const { user, system } = process.cpuUsage();
|
|
391
|
+
const cpuMs = Math.round((user + system) / 1e3);
|
|
392
|
+
if (Number.isFinite(cpuMs) && cpuMs > 0) usage.cpuTimeMs = cpuMs;
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
return usage;
|
|
396
|
+
}
|
|
397
|
+
function readStartupDurationMs() {
|
|
398
|
+
try {
|
|
399
|
+
const ms = Math.round(process.uptime() * 1e3);
|
|
400
|
+
return Number.isFinite(ms) && ms > 0 ? ms : void 0;
|
|
401
|
+
} catch {
|
|
402
|
+
return void 0;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function readBinarySizeBytes() {
|
|
406
|
+
try {
|
|
407
|
+
const sea = process.getBuiltinModule?.("node:sea");
|
|
408
|
+
if (!sea?.isSea()) return void 0;
|
|
409
|
+
const size = statSync(process.execPath).size;
|
|
410
|
+
return Number.isFinite(size) && size > 0 ? size : void 0;
|
|
411
|
+
} catch {
|
|
412
|
+
return void 0;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
233
415
|
function newSessionId() {
|
|
234
416
|
try {
|
|
235
417
|
return randomBytes(16).toString("hex");
|
|
@@ -237,6 +419,24 @@ function newSessionId() {
|
|
|
237
419
|
return "";
|
|
238
420
|
}
|
|
239
421
|
}
|
|
422
|
+
var INSTALL_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
423
|
+
function loadOrCreateInstallId(path) {
|
|
424
|
+
if (!path) return "";
|
|
425
|
+
try {
|
|
426
|
+
const existing = readFileSync(path, "utf8").trim();
|
|
427
|
+
if (INSTALL_ID_PATTERN.test(existing)) return existing;
|
|
428
|
+
} catch {
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
const id = randomUUID();
|
|
432
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
433
|
+
writeFileSync(path, `${id}
|
|
434
|
+
`, { mode: 384 });
|
|
435
|
+
return id;
|
|
436
|
+
} catch {
|
|
437
|
+
return "";
|
|
438
|
+
}
|
|
439
|
+
}
|
|
240
440
|
|
|
241
441
|
// src/safety.ts
|
|
242
442
|
var ARGVIO_DEBUG = process.env["ARGVIO_DEBUG"] === "1";
|
|
@@ -354,14 +554,21 @@ var Client = class {
|
|
|
354
554
|
shuttingDown = false;
|
|
355
555
|
shutdownStarted = false;
|
|
356
556
|
sessionId;
|
|
557
|
+
sessionStarted = false;
|
|
558
|
+
cliName;
|
|
559
|
+
installIdPath;
|
|
560
|
+
installMethod;
|
|
561
|
+
installId;
|
|
562
|
+
activatedFeatures = /* @__PURE__ */ new Set();
|
|
563
|
+
tracer;
|
|
357
564
|
tracerProvider;
|
|
358
565
|
meterProvider;
|
|
359
566
|
loggerProvider;
|
|
360
567
|
logger;
|
|
361
568
|
latencyHist;
|
|
362
569
|
invocationsCounter;
|
|
363
|
-
sessionsCounter;
|
|
364
570
|
errorEventsCounter;
|
|
571
|
+
productCounter;
|
|
365
572
|
/** @internal use {@link createClient} */
|
|
366
573
|
constructor(params) {
|
|
367
574
|
this.disabled = params.disabled;
|
|
@@ -375,8 +582,12 @@ var Client = class {
|
|
|
375
582
|
this.logger = params.logger;
|
|
376
583
|
this.latencyHist = params.latencyHist;
|
|
377
584
|
this.invocationsCounter = params.invocationsCounter;
|
|
378
|
-
this.sessionsCounter = params.sessionsCounter;
|
|
379
585
|
this.errorEventsCounter = params.errorEventsCounter;
|
|
586
|
+
this.productCounter = params.productCounter;
|
|
587
|
+
this.cliName = params.cliName ?? "";
|
|
588
|
+
this.installIdPath = params.installIdPath;
|
|
589
|
+
this.installMethod = params.installMethod;
|
|
590
|
+
this.tracer = params.tracer;
|
|
380
591
|
}
|
|
381
592
|
enabled() {
|
|
382
593
|
return !this.disabled && !this.shuttingDown;
|
|
@@ -398,109 +609,193 @@ var Client = class {
|
|
|
398
609
|
}
|
|
399
610
|
emitEvent(name, severity, attributes = {}) {
|
|
400
611
|
this.queue.push(() => {
|
|
401
|
-
this.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
612
|
+
this.emitEventNow(name, severity, attributes);
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
emitEventNow(name, severity, attributes = {}) {
|
|
616
|
+
this.logger?.emit({
|
|
617
|
+
eventName: name,
|
|
618
|
+
timestamp: Date.now(),
|
|
619
|
+
severityNumber: severity,
|
|
620
|
+
attributes: { ...this.tierAttr(), ...attributes }
|
|
407
621
|
});
|
|
408
622
|
}
|
|
623
|
+
name(commandPath) {
|
|
624
|
+
return commandName(this.cliName, commandPath);
|
|
625
|
+
}
|
|
409
626
|
/**
|
|
410
|
-
* Lazily generates a session correlation ID at `Tier.
|
|
627
|
+
* Lazily generates a session correlation ID at `Tier.Basic`+. A random,
|
|
411
628
|
* non-persistent identifier scoped to a single process invocation —
|
|
412
629
|
* not a user or device identifier.
|
|
413
630
|
*/
|
|
414
631
|
ensureSessionId() {
|
|
415
|
-
if (!tierAllows(this.tier, Tier.
|
|
632
|
+
if (!tierAllows(this.tier, Tier.Basic)) return "";
|
|
416
633
|
if (this.sessionId === void 0) {
|
|
417
634
|
this.sessionId = newSessionId();
|
|
418
635
|
}
|
|
419
636
|
return this.sessionId;
|
|
420
637
|
}
|
|
421
638
|
/**
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
|
|
426
|
-
|
|
639
|
+
* The session and install ID attributes for a record, empty below
|
|
640
|
+
* `Tier.Basic`. May read or create the install ID file, so call it from
|
|
641
|
+
* inside a queued job rather than on the caller's stack.
|
|
642
|
+
*/
|
|
643
|
+
identityAttrs(sessionId) {
|
|
644
|
+
const attrs = {};
|
|
645
|
+
if (sessionId) attrs[AttrSessionId] = sessionId;
|
|
646
|
+
if (tierAllows(this.tier, Tier.Basic)) {
|
|
647
|
+
this.installId ??= loadOrCreateInstallId(this.installIdPath);
|
|
648
|
+
if (this.installId) attrs[AttrInstallId] = this.installId;
|
|
649
|
+
}
|
|
650
|
+
return attrs;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Records the start of a CLI process session, which is what the server
|
|
654
|
+
* counts sessions, active installs and retention from. Call it as early
|
|
655
|
+
* as possible (the yargs/commander integrations do it for you); only the
|
|
656
|
+
* first call per Client emits anything.
|
|
657
|
+
*
|
|
658
|
+
* At `Tier.Anonymous` the event carries no identifiers. At `Tier.Basic`+
|
|
659
|
+
* it also carries the per-process session ID and the install ID, a
|
|
660
|
+
* random UUID persisted in the CLI's config directory the first time it
|
|
661
|
+
* is needed — never derived from the user or machine.
|
|
662
|
+
*
|
|
663
|
+
* At `Tier.Basic`+ it also carries startup overhead
|
|
664
|
+
* (`cli.startup.duration_ms`): the time from process start to this call,
|
|
665
|
+
* so it covers module loading and framework setup. Call it just before
|
|
666
|
+
* the command runs, as the integrations do. And when running as a Node
|
|
667
|
+
* single executable application, it carries the binary's size
|
|
668
|
+
* (`cli.binary.size_bytes`). Either is omitted if unavailable, never 0.
|
|
427
669
|
*/
|
|
428
670
|
recordSessionStart() {
|
|
429
671
|
safeRun(() => {
|
|
430
|
-
if (!this.enabled()) return;
|
|
672
|
+
if (!this.enabled() || this.sessionStarted) return;
|
|
673
|
+
this.sessionStarted = true;
|
|
674
|
+
const sessionId = this.ensureSessionId();
|
|
675
|
+
const basic = tierAllows(this.tier, Tier.Basic);
|
|
676
|
+
const startupMs = basic ? readStartupDurationMs() : void 0;
|
|
431
677
|
this.queue.push(() => {
|
|
432
|
-
|
|
678
|
+
const attrs = this.identityAttrs(sessionId);
|
|
679
|
+
if (startupMs !== void 0) attrs[AttrStartupDurationMs] = startupMs;
|
|
680
|
+
if (basic) {
|
|
681
|
+
const sizeBytes = readBinarySizeBytes();
|
|
682
|
+
if (sizeBytes !== void 0) attrs[AttrBinarySizeBytes] = sizeBytes;
|
|
683
|
+
}
|
|
684
|
+
if (this.installMethod) attrs[AttrInstallMethod] = this.installMethod;
|
|
685
|
+
this.emitEventNow(EventSessionStarted, SeverityNumber.INFO, attrs);
|
|
433
686
|
});
|
|
434
|
-
if (!tierAllows(this.tier, Tier.Basic)) return;
|
|
435
|
-
const attrs = {};
|
|
436
|
-
const id = this.ensureSessionId();
|
|
437
|
-
if (id) attrs[AttrSessionId] = id;
|
|
438
|
-
this.emitEvent("cli.session_start", SeverityNumber.INFO, attrs);
|
|
439
|
-
}, this.counters);
|
|
440
|
-
}
|
|
441
|
-
/** Records the end of a CLI process session. Call once, symmetric with {@link recordSessionStart}. */
|
|
442
|
-
recordSessionEnd() {
|
|
443
|
-
safeRun(() => {
|
|
444
|
-
if (!this.enabled() || !tierAllows(this.tier, Tier.Basic)) return;
|
|
445
|
-
const attrs = {};
|
|
446
|
-
const id = this.ensureSessionId();
|
|
447
|
-
if (id) attrs[AttrSessionId] = id;
|
|
448
|
-
this.emitEvent("cli.session_end", SeverityNumber.INFO, attrs);
|
|
449
687
|
}, this.counters);
|
|
450
688
|
}
|
|
451
689
|
/**
|
|
452
690
|
* Records that a command began executing. `commandPath` should be the
|
|
453
|
-
* full command path (e.g. `"mycli sub subsub"`)
|
|
454
|
-
* command name. `flagNames` is the list of flag
|
|
455
|
-
* never flag values, which this SDK never
|
|
456
|
-
* taxonomy.
|
|
691
|
+
* full command path (e.g. `"mycli sub subsub"`); it is reported as the
|
|
692
|
+
* dotted command name (`"sub.subsub"`). `flagNames` is the list of flag
|
|
693
|
+
* *names* the user set — never flag values, which this SDK never
|
|
694
|
+
* accepts anywhere in the taxonomy.
|
|
457
695
|
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
* path and flag names are included.
|
|
696
|
+
* The command name is recorded at every tier; flag names and the
|
|
697
|
+
* session ID at `Tier.Basic`+.
|
|
461
698
|
*/
|
|
462
699
|
recordCommandInvocation(commandPath, flagNames = []) {
|
|
463
700
|
safeRun(() => {
|
|
464
701
|
if (!this.enabled()) return;
|
|
702
|
+
const attrs = { [AttrCommandName]: this.name(commandPath) };
|
|
703
|
+
if (tierAllows(this.tier, Tier.Basic) && flagNames.length > 0) {
|
|
704
|
+
attrs[AttrFlagNames] = [...flagNames];
|
|
705
|
+
}
|
|
706
|
+
const sessionId = this.ensureSessionId();
|
|
465
707
|
this.queue.push(() => {
|
|
466
|
-
this.
|
|
708
|
+
this.emitEventNow(EventCommandInvoked, SeverityNumber.INFO, {
|
|
709
|
+
...attrs,
|
|
710
|
+
...this.identityAttrs(sessionId)
|
|
711
|
+
});
|
|
467
712
|
});
|
|
468
|
-
if (!tierAllows(this.tier, Tier.Basic)) return;
|
|
469
|
-
const attrs = { [AttrCommandPath]: commandPath };
|
|
470
|
-
if (flagNames.length > 0) attrs[AttrCommandFlags] = [...flagNames];
|
|
471
|
-
this.emitEvent("cli.command_invocation", SeverityNumber.INFO, attrs);
|
|
472
713
|
}, this.counters);
|
|
473
714
|
}
|
|
474
715
|
/**
|
|
475
|
-
* Records
|
|
476
|
-
*
|
|
477
|
-
* `
|
|
478
|
-
*
|
|
716
|
+
* Records a completed command: a `cli.command.<name>` span covering its
|
|
717
|
+
* execution, plus the `cli.command.invocations` and
|
|
718
|
+
* `cli.command.duration` metrics. This is what the server's command
|
|
719
|
+
* frequency, latency, error-rate and exit-code dashboards are built
|
|
720
|
+
* from, so call it once for every command that ran (the integrations
|
|
721
|
+
* do). Available at every tier; flag names, peak memory and CPU time
|
|
722
|
+
* only at `Tier.Basic`+.
|
|
723
|
+
*
|
|
724
|
+
* Peak memory (`cli.process.peak_rss_bytes`) and CPU time
|
|
725
|
+
* (`cli.process.cpu_time_ms`) are read once from the process, so they are
|
|
726
|
+
* process-wide and cumulative: right for a CLI that runs one command per
|
|
727
|
+
* process, but in a long-lived or multi-command process each call
|
|
728
|
+
* reports totals since the process started. A measure the platform can't
|
|
729
|
+
* provide is omitted.
|
|
479
730
|
*/
|
|
480
|
-
|
|
731
|
+
recordCommand(result) {
|
|
481
732
|
safeRun(() => {
|
|
482
|
-
if (!this.
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
733
|
+
if (!this.enabled()) return;
|
|
734
|
+
const durationMs = Math.max(0, result.durationMs);
|
|
735
|
+
const startMs = result.startMs ?? Date.now() - durationMs;
|
|
736
|
+
const name = this.name(result.path);
|
|
737
|
+
const attrs = {
|
|
738
|
+
...this.tierAttr(),
|
|
739
|
+
[AttrCommandName]: name,
|
|
740
|
+
[AttrExitCode]: result.exitCode
|
|
741
|
+
};
|
|
742
|
+
const spanAttrs = { ...attrs, [AttrDurationMs]: Math.round(durationMs) };
|
|
743
|
+
if (tierAllows(this.tier, Tier.Basic) && result.flagNames && result.flagNames.length > 0) {
|
|
744
|
+
spanAttrs[AttrFlagNames] = [...result.flagNames];
|
|
745
|
+
}
|
|
746
|
+
if (tierAllows(this.tier, Tier.Basic)) {
|
|
747
|
+
const { peakRssBytes, cpuTimeMs } = readProcessResourceUsage();
|
|
748
|
+
if (peakRssBytes !== void 0) spanAttrs[AttrPeakRssBytes] = peakRssBytes;
|
|
749
|
+
if (cpuTimeMs !== void 0) spanAttrs[AttrCpuTimeMs] = cpuTimeMs;
|
|
486
750
|
}
|
|
487
|
-
const
|
|
488
|
-
this.
|
|
751
|
+
const sessionId = this.ensureSessionId();
|
|
752
|
+
this.queue.push(() => {
|
|
753
|
+
const span = this.tracer?.startSpan(SpanCommandPrefix + name, {
|
|
754
|
+
startTime: startMs,
|
|
755
|
+
attributes: spanAttrs
|
|
756
|
+
});
|
|
757
|
+
if (result.exitCode !== 0) span?.setStatus({ code: SpanStatusCode.ERROR });
|
|
758
|
+
span?.end(startMs + durationMs);
|
|
759
|
+
this.invocationsCounter?.add(1, attrs);
|
|
760
|
+
this.latencyHist?.record(durationMs, attrs);
|
|
761
|
+
this.emitEventNow(EventCommandCompleted, SeverityNumber.INFO, {
|
|
762
|
+
[AttrCommandName]: name,
|
|
763
|
+
[AttrExitCode]: result.exitCode,
|
|
764
|
+
[AttrDurationMs]: Math.round(durationMs),
|
|
765
|
+
...this.identityAttrs(sessionId)
|
|
766
|
+
});
|
|
767
|
+
});
|
|
489
768
|
}, this.counters);
|
|
490
769
|
}
|
|
491
770
|
/**
|
|
492
|
-
* Records
|
|
493
|
-
*
|
|
771
|
+
* Records that the user used one of the vendor's features, so the server
|
|
772
|
+
* can tell which features an install has adopted and how soon after
|
|
773
|
+
* installing. Emitted at `Tier.Basic`+ only; nothing is sent at
|
|
774
|
+
* `Tier.Anonymous`. The event carries the feature name, the command name
|
|
775
|
+
* when `commandPath` is given, and the session and install IDs.
|
|
776
|
+
*
|
|
777
|
+
* `feature` must be a fixed identifier chosen by the vendor in code
|
|
778
|
+
* (e.g. `"export-csv"`), **never user input** such as a flag value, path
|
|
779
|
+
* or argument. It is truncated to the server's 64 character limit.
|
|
780
|
+
*
|
|
781
|
+
* Each feature is reported at most once per process, however often this
|
|
782
|
+
* is called, to keep volume down. The server derives "first use" per
|
|
783
|
+
* install itself, so repeat reports within a run add nothing.
|
|
494
784
|
*/
|
|
495
|
-
|
|
785
|
+
recordFeatureActivated(feature, commandPath) {
|
|
496
786
|
safeRun(() => {
|
|
497
|
-
if (!this.gate(Tier.
|
|
498
|
-
const
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
}
|
|
787
|
+
if (!this.gate(Tier.Basic)) return;
|
|
788
|
+
const name = feature.slice(0, MAX_ACTIVATION_FEATURE_LENGTH);
|
|
789
|
+
if (!name || this.activatedFeatures.has(name)) return;
|
|
790
|
+
this.activatedFeatures.add(name);
|
|
791
|
+
const attrs = { [AttrActivationFeature]: name };
|
|
792
|
+
if (commandPath) attrs[AttrCommandName] = this.name(commandPath);
|
|
793
|
+
const sessionId = this.ensureSessionId();
|
|
502
794
|
this.queue.push(() => {
|
|
503
|
-
this.
|
|
795
|
+
this.emitEventNow(EventFeatureActivated, SeverityNumber.INFO, {
|
|
796
|
+
...attrs,
|
|
797
|
+
...this.identityAttrs(sessionId)
|
|
798
|
+
});
|
|
504
799
|
});
|
|
505
800
|
}, this.counters);
|
|
506
801
|
}
|
|
@@ -508,11 +803,10 @@ var Client = class {
|
|
|
508
803
|
recordHelpFlagUsage(commandPath) {
|
|
509
804
|
safeRun(() => {
|
|
510
805
|
if (!this.gate(Tier.Anonymous)) return;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
this.emitEvent("cli.help_flag_used", SeverityNumber.INFO, attrs);
|
|
806
|
+
this.emitEvent(EventHelpViewed, SeverityNumber.INFO, {
|
|
807
|
+
[AttrCommandName]: this.name(commandPath),
|
|
808
|
+
[AttrHelpFlag]: "help"
|
|
809
|
+
});
|
|
516
810
|
}, this.counters);
|
|
517
811
|
}
|
|
518
812
|
/**
|
|
@@ -521,19 +815,73 @@ var Client = class {
|
|
|
521
815
|
* deliberately the only error-recording capability available below
|
|
522
816
|
* `Tier.OptIn`. Raw error detail is only reachable via {@link optIn}.
|
|
523
817
|
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
818
|
+
* At every tier this counts the error on the `cli.errors` metric by
|
|
819
|
+
* command. At `Tier.Basic`+ the category is attached as the error type
|
|
820
|
+
* and a `cli.error.raised` event is emitted too, tied to the session and
|
|
821
|
+
* install.
|
|
822
|
+
*
|
|
823
|
+
* `reason` is an optional, finer classification from a fixed vocabulary
|
|
824
|
+
* you define (e.g. `"no-token"`, `"invalid-url"`), sent at `Tier.Basic`+
|
|
825
|
+
* as `cli.error.reason` on both of those. It must be an identifier chosen
|
|
826
|
+
* in code, **never error text or user input**: this SDK does not scrub it.
|
|
827
|
+
* It must match `^[a-z0-9][a-z0-9._-]*$` and be at most 64 bytes; one that
|
|
828
|
+
* doesn't is dropped (the error is still recorded, without a reason).
|
|
526
829
|
*/
|
|
527
|
-
recordError(commandPath, category = ErrorCategory.Unknown) {
|
|
830
|
+
recordError(commandPath, category = ErrorCategory.Unknown, reason) {
|
|
528
831
|
safeRun(() => {
|
|
529
|
-
if (!this.
|
|
832
|
+
if (!this.enabled()) return;
|
|
530
833
|
const cat = isValidErrorCategory(category) ? category : ErrorCategory.Unknown;
|
|
531
|
-
const
|
|
532
|
-
|
|
834
|
+
const basic = tierAllows(this.tier, Tier.Basic);
|
|
835
|
+
const attrs = { [AttrCommandName]: this.name(commandPath) };
|
|
836
|
+
if (basic) {
|
|
837
|
+
attrs[AttrErrorType] = cat;
|
|
838
|
+
if (isValidIdentifier(reason)) attrs[AttrErrorReason] = reason;
|
|
839
|
+
}
|
|
840
|
+
const sessionId = this.ensureSessionId();
|
|
533
841
|
this.queue.push(() => {
|
|
534
|
-
this.errorEventsCounter?.add(1, { ...this.tierAttr(),
|
|
842
|
+
this.errorEventsCounter?.add(1, { ...this.tierAttr(), ...attrs });
|
|
843
|
+
if (basic) {
|
|
844
|
+
this.emitEventNow(EventErrorRaised, SeverityNumber.ERROR, {
|
|
845
|
+
...attrs,
|
|
846
|
+
...this.identityAttrs(sessionId)
|
|
847
|
+
});
|
|
848
|
+
}
|
|
535
849
|
});
|
|
536
|
-
|
|
850
|
+
}, this.counters);
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Adds `n` to a vendor-defined counter, sent as the `cli.counter` metric
|
|
854
|
+
* with the `cli.counter.name` and, if given, `cli.counter.label`
|
|
855
|
+
* attributes. Increments are summed in memory: one data point per name
|
|
856
|
+
* and label is sent at each export, however often this is called, so it
|
|
857
|
+
* is cheap in a hot loop.
|
|
858
|
+
*
|
|
859
|
+
* **Counters are accepted at the anonymous tier.** They are sent at every
|
|
860
|
+
* tier, including `Tier.Anonymous` and in CI, even when the user has made
|
|
861
|
+
* no consent choice. Nothing identifies the user, but `name` and `label`
|
|
862
|
+
* are strings you choose, so a mistake here puts text into anonymous
|
|
863
|
+
* data. They must be fixed identifiers chosen in code, **never user
|
|
864
|
+
* input, file paths, host names, URLs, tokens or anything derived from
|
|
865
|
+
* them**. This SDK does not scrub them.
|
|
866
|
+
*
|
|
867
|
+
* `name` and `label` must match `^[a-z0-9][a-z0-9._-]*$` and be at most
|
|
868
|
+
* 64 bytes. If either does not, nothing is recorded: the increment is
|
|
869
|
+
* dropped, not trimmed or sent without its label. `n` must be a positive
|
|
870
|
+
* integer; anything else is ignored. The server caps the number of
|
|
871
|
+
* distinct names and labels per tenant and folds the rest into `other`.
|
|
872
|
+
*
|
|
873
|
+
* Never throws, and does nothing on a disabled Client.
|
|
874
|
+
*/
|
|
875
|
+
recordCounter(name, n, options = {}) {
|
|
876
|
+
safeRun(() => {
|
|
877
|
+
if (!this.gate(Tier.Anonymous)) return;
|
|
878
|
+
if (!isValidIdentifier(name)) return;
|
|
879
|
+
if (typeof n !== "number" || !Number.isSafeInteger(n) || n <= 0) return;
|
|
880
|
+
const label = options?.label;
|
|
881
|
+
if (label !== void 0 && !isValidIdentifier(label)) return;
|
|
882
|
+
const attrs = { ...this.tierAttr(), [AttrCounterName]: name };
|
|
883
|
+
if (label !== void 0) attrs[AttrCounterLabel] = label;
|
|
884
|
+
this.productCounter?.add(n, attrs);
|
|
537
885
|
}, this.counters);
|
|
538
886
|
}
|
|
539
887
|
/**
|
|
@@ -551,10 +899,12 @@ var Client = class {
|
|
|
551
899
|
_recordErrorDetail(commandPath, err, stack) {
|
|
552
900
|
safeRun(() => {
|
|
553
901
|
if (!this.gate(Tier.OptIn)) return;
|
|
554
|
-
const attrs = {
|
|
555
|
-
|
|
902
|
+
const attrs = {
|
|
903
|
+
[AttrCommandName]: this.name(commandPath),
|
|
904
|
+
[AttrErrorMessage]: err.message
|
|
905
|
+
};
|
|
556
906
|
if (stack) attrs[AttrErrorStack] = stack;
|
|
557
|
-
this.emitEvent(
|
|
907
|
+
this.emitEvent(EventErrorRaised, SeverityNumber.ERROR, attrs);
|
|
558
908
|
}, this.counters);
|
|
559
909
|
}
|
|
560
910
|
/**
|
|
@@ -567,13 +917,11 @@ var Client = class {
|
|
|
567
917
|
if (!this.enabled()) return;
|
|
568
918
|
await this.queue.waitForDrain(timeoutMs);
|
|
569
919
|
await withTimeout(
|
|
570
|
-
Promise.
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
].filter((p) => Boolean(p))
|
|
576
|
-
).then(() => void 0),
|
|
920
|
+
Promise.allSettled([
|
|
921
|
+
this.tracerProvider?.forceFlush(),
|
|
922
|
+
this.meterProvider?.forceFlush(),
|
|
923
|
+
this.loggerProvider?.forceFlush()
|
|
924
|
+
]).then(() => void 0),
|
|
577
925
|
timeoutMs
|
|
578
926
|
);
|
|
579
927
|
}
|
|
@@ -589,13 +937,11 @@ var Client = class {
|
|
|
589
937
|
this.shuttingDown = true;
|
|
590
938
|
await this.queue.waitForDrain(timeoutMs);
|
|
591
939
|
await withTimeout(
|
|
592
|
-
Promise.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
].filter((p) => Boolean(p))
|
|
598
|
-
).then(() => void 0),
|
|
940
|
+
Promise.allSettled([
|
|
941
|
+
this.tracerProvider?.shutdown(),
|
|
942
|
+
this.meterProvider?.shutdown(),
|
|
943
|
+
this.loggerProvider?.shutdown()
|
|
944
|
+
]).then(() => void 0),
|
|
599
945
|
timeoutMs
|
|
600
946
|
);
|
|
601
947
|
}
|
|
@@ -672,6 +1018,10 @@ function createClient(apiKey, cliName, cliVersion, options = {}) {
|
|
|
672
1018
|
const meter = meterProvider.getMeter(INSTRUMENTATION_NAME);
|
|
673
1019
|
const logger = loggerProvider.getLogger(INSTRUMENTATION_NAME);
|
|
674
1020
|
return new Client({
|
|
1021
|
+
cliName,
|
|
1022
|
+
installIdPath: cfg.installIdPath,
|
|
1023
|
+
installMethod: cfg.installMethod,
|
|
1024
|
+
tracer: tracerProvider.getTracer(INSTRUMENTATION_NAME),
|
|
675
1025
|
disabled: false,
|
|
676
1026
|
initError: null,
|
|
677
1027
|
tier,
|
|
@@ -685,14 +1035,17 @@ function createClient(apiKey, cliName, cliVersion, options = {}) {
|
|
|
685
1035
|
unit: "ms",
|
|
686
1036
|
description: "Duration of a CLI command invocation."
|
|
687
1037
|
}),
|
|
688
|
-
invocationsCounter: meter.createCounter(
|
|
689
|
-
|
|
1038
|
+
invocationsCounter: meter.createCounter(MetricCommandInvocations, {
|
|
1039
|
+
unit: "1",
|
|
1040
|
+
description: "Count of completed CLI command invocations."
|
|
690
1041
|
}),
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}),
|
|
694
|
-
errorEventsCounter: meter.createCounter("cli.errors", {
|
|
1042
|
+
errorEventsCounter: meter.createCounter(MetricErrors, {
|
|
1043
|
+
unit: "1",
|
|
695
1044
|
description: "Count of recorded CLI errors."
|
|
1045
|
+
}),
|
|
1046
|
+
productCounter: meter.createCounter(MetricCounter, {
|
|
1047
|
+
unit: "1",
|
|
1048
|
+
description: "Vendor-defined counters, by fixed name and optional label."
|
|
696
1049
|
})
|
|
697
1050
|
});
|
|
698
1051
|
} catch (err) {
|
|
@@ -730,56 +1083,7 @@ function chainProviders(...providers) {
|
|
|
730
1083
|
}
|
|
731
1084
|
};
|
|
732
1085
|
}
|
|
733
|
-
function userConfigDir() {
|
|
734
|
-
switch (process.platform) {
|
|
735
|
-
case "win32":
|
|
736
|
-
return process.env["APPDATA"];
|
|
737
|
-
case "darwin":
|
|
738
|
-
return join(homedir(), "Library", "Application Support");
|
|
739
|
-
default:
|
|
740
|
-
return process.env["XDG_CONFIG_HOME"] || join(homedir(), ".config");
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
function defaultConsentPath(cliName) {
|
|
744
|
-
const dir = userConfigDir();
|
|
745
|
-
if (!dir) return void 0;
|
|
746
|
-
return join(dir, cliName || "argvio", "argvio-consent.json");
|
|
747
|
-
}
|
|
748
|
-
var FileConsentProvider = class {
|
|
749
|
-
/** The resolved config file path (after defaults/overrides), primarily for diagnostics and tests. */
|
|
750
|
-
path;
|
|
751
|
-
constructor(cliName, options = {}) {
|
|
752
|
-
this.path = options.path || defaultConsentPath(cliName);
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* A missing file, unreadable file, or malformed contents all throw
|
|
756
|
-
* synchronously, so callers should place `FileConsentProvider` inside
|
|
757
|
-
* `chainProviders` with a sane fallback (e.g.
|
|
758
|
-
* `new StaticProvider(Tier.Anonymous)`).
|
|
759
|
-
*/
|
|
760
|
-
resolve() {
|
|
761
|
-
if (!this.path) throw errNoConsentFile;
|
|
762
|
-
const data = readFileSync(this.path, "utf8");
|
|
763
|
-
const parsed = JSON.parse(data);
|
|
764
|
-
const tier = parseTier(parsed.tier ?? "");
|
|
765
|
-
if (tier === void 0) throw errUnknownTierValue;
|
|
766
|
-
return tier;
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* Persists `tier` as the chosen consent tier, creating the parent
|
|
770
|
-
* directory if needed. Intended for use by a vendor's first-run
|
|
771
|
-
* consent prompt. Does not itself enforce any tier ceiling — it's the
|
|
772
|
-
* vendor's own UX writing the user's explicit choice.
|
|
773
|
-
*/
|
|
774
|
-
store(tier) {
|
|
775
|
-
if (!this.path) throw errNoConsentFile;
|
|
776
|
-
if (!isValidTier(tier)) throw errUnknownTierValue;
|
|
777
|
-
mkdirSync(dirname(this.path), { recursive: true, mode: 448 });
|
|
778
|
-
const contents = { tier: tierToString(tier) };
|
|
779
|
-
writeFileSync(this.path, JSON.stringify(contents), { mode: 384 });
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
1086
|
|
|
783
|
-
export {
|
|
1087
|
+
export { AttrActivationFeature, AttrBinarySizeBytes, AttrCommandName, AttrCounterLabel, AttrCounterName, AttrCpuTimeMs, AttrDurationMs, AttrErrorMessage, AttrErrorReason, AttrErrorStack, AttrErrorType, AttrExitCode, AttrFlagNames, AttrHelpFlag, AttrInstallId, AttrInstallMethod, AttrPeakRssBytes, AttrSessionId, AttrStartupDurationMs, Client, DEFAULT_AUTH_HEADER, DEFAULT_ENDPOINT_GRPC, DEFAULT_ENDPOINT_HTTP, DEFAULT_EXPORT_TIMEOUT_MS, DEFAULT_QUEUE_SIZE, DEFAULT_SHUTDOWN_TIMEOUT_MS, DisabledProvider, EnvConsentProvider, ErrorCategory, EventCommandCompleted, EventCommandInvoked, EventErrorRaised, EventFeatureActivated, EventHelpViewed, EventSessionStarted, FileConsentProvider, MetricCommandDuration, MetricCommandInvocations, MetricCounter, MetricErrors, OptInScope, RESOURCE_ATTR_TIER, ResourceAttrArch, ResourceAttrCIProvider, ResourceAttrCLIVersion, ResourceAttrIsCI, ResourceAttrOS, SpanCommandPrefix, StaticProvider, Tier, chainProviders, clampTier, createClient, detectCIProvider, isCI, isDoNotTrackRequested, isValidErrorCategory, isValidTier, parseTier, tierAllows, tierToString };
|
|
784
1088
|
//# sourceMappingURL=index.js.map
|
|
785
1089
|
//# sourceMappingURL=index.js.map
|