@cedarjs/cli-helpers 8.0.0-canary.3592 → 8.0.0-canary.3595
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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AttributeValue, Span } from '@opentelemetry/api';
|
|
2
|
+
export { redactError, redactErrorMessage } from './redact.js';
|
|
2
3
|
type TelemetryAttributes = {
|
|
3
4
|
[key: string]: AttributeValue | undefined;
|
|
4
5
|
};
|
|
@@ -12,9 +13,12 @@ export declare function recordTelemetryAttributes(attributes: TelemetryAttribute
|
|
|
12
13
|
/**
|
|
13
14
|
* Safely records an error to the opentelemetry span
|
|
14
15
|
*
|
|
16
|
+
* Only a redacted version of the error is recorded: the first line of the
|
|
17
|
+
* message, with URLs and file paths replaced by placeholders, and no stack
|
|
18
|
+
* trace. See `redactErrorMessage`.
|
|
19
|
+
*
|
|
15
20
|
* @param error An error to record to the span
|
|
16
21
|
* @param span An optional span to record the error to. If not provided, the current active span will be used
|
|
17
22
|
*/
|
|
18
|
-
export declare function recordTelemetryError(error:
|
|
19
|
-
export {};
|
|
23
|
+
export declare function recordTelemetryError(error: unknown, span?: Span): void;
|
|
20
24
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAK9D,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAE7D,KAAK,mBAAmB,GAAG;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAA;CAC1C,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,mBAAmB,EAC/B,IAAI,CAAC,EAAE,IAAI,QAaZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,QAa/D"}
|
package/dist/telemetry/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import opentelemetry, { SpanStatusCode } from "@opentelemetry/api";
|
|
2
|
+
import { redactError } from "./redact.js";
|
|
3
|
+
import { redactError as redactError2, redactErrorMessage } from "./redact.js";
|
|
2
4
|
function recordTelemetryAttributes(attributes, span) {
|
|
3
5
|
const spanToRecord = span ?? opentelemetry.trace.getActiveSpan();
|
|
4
6
|
if (spanToRecord === void 0) {
|
|
@@ -15,15 +17,16 @@ function recordTelemetryError(error, span) {
|
|
|
15
17
|
if (spanToRecord === void 0) {
|
|
16
18
|
return;
|
|
17
19
|
}
|
|
18
|
-
const
|
|
19
|
-
const firstLineOfError = message.split("\n")[0];
|
|
20
|
+
const redacted = redactError(error ?? "Unknown error");
|
|
20
21
|
spanToRecord.setStatus({
|
|
21
22
|
code: SpanStatusCode.ERROR,
|
|
22
|
-
message:
|
|
23
|
+
message: redacted.message
|
|
23
24
|
});
|
|
24
|
-
spanToRecord.recordException(
|
|
25
|
+
spanToRecord.recordException(redacted);
|
|
25
26
|
}
|
|
26
27
|
export {
|
|
27
28
|
recordTelemetryAttributes,
|
|
28
|
-
recordTelemetryError
|
|
29
|
+
recordTelemetryError,
|
|
30
|
+
redactError2 as redactError,
|
|
31
|
+
redactErrorMessage
|
|
29
32
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns an error (or any thrown value) into a message that is safe to send
|
|
3
|
+
* with telemetry.
|
|
4
|
+
*
|
|
5
|
+
* Only the first line is kept, because some errors include the full stack
|
|
6
|
+
* trace in the message. Then every word that could identify a user, their
|
|
7
|
+
* project or their infrastructure is replaced: anything containing `/`, `\`,
|
|
8
|
+
* `@` or `:`, and IPv4 addresses. Replacements are labelled `[url]`, `[host]`,
|
|
9
|
+
* `[path]` or `[redacted]`. Quoted paths are replaced as a whole, since they
|
|
10
|
+
* can contain spaces, and so are hostnames in DNS errors.
|
|
11
|
+
*
|
|
12
|
+
* Words that describe the failure without identifying anyone are kept:
|
|
13
|
+
* scoped package names (`@cedarjs/vite`), known specifiers (`node:fs`),
|
|
14
|
+
* version numbers, clock times and words that only end in a colon (`Error:`).
|
|
15
|
+
* What is left is the shape of the error, e.g.
|
|
16
|
+
* `ENOENT: no such file or directory, open '[path]'`.
|
|
17
|
+
*
|
|
18
|
+
* This module has no imports so it can be bundled into create-cedar-app
|
|
19
|
+
* without pulling in anything else.
|
|
20
|
+
*/
|
|
21
|
+
export declare function redactErrorMessage(error: unknown): string;
|
|
22
|
+
/**
|
|
23
|
+
* Returns a new Error that only carries the redacted message of `error`.
|
|
24
|
+
* The original stack trace is left out, since it contains file paths.
|
|
25
|
+
*/
|
|
26
|
+
export declare function redactError(error: unknown): Error;
|
|
27
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/telemetry/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAwBzD;AA2FD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAYjD"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
function redactErrorMessage(error) {
|
|
2
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : String(error);
|
|
3
|
+
return (message.split("\n")[0] || "Unknown error").replace(
|
|
4
|
+
/(['"`])([^'"`\\/\n]*[\\/][^'"`\n]*)\1/g,
|
|
5
|
+
(_match, quote, inner) => `${quote}${isSafeWord(inner) ? inner : placeholderFor(inner)}${quote}`
|
|
6
|
+
).replace(/\b(ENOTFOUND|EAI_AGAIN)\s+[^\s'"`]+/g, "$1 [host]").replace(/\S+/g, redactWord);
|
|
7
|
+
}
|
|
8
|
+
const SCOPED_PACKAGE = /^@[a-z0-9~-][\w.~-]*\/[a-z0-9~-][\w.~-]*(?:\/[\w.-]+)*(?:@[\w.^~*<>=-]+)?$/i;
|
|
9
|
+
const KNOWN_SPECIFIER = /^(?:node|npm|workspace|catalog):[\w.^~*<>=-]*$/;
|
|
10
|
+
const CLOCK_TIME = /^\d{1,2}:\d{2}(?::\d{2})?$/;
|
|
11
|
+
const IPV4 = /(?:^|[^\d.])\d{1,3}(?:\.\d{1,3}){3}(?![.\d])/;
|
|
12
|
+
const IPV6 = /^\[?[\da-f]*:[\da-f]*:[\da-f:]*\]?(?::\d+)?$/i;
|
|
13
|
+
const HOST_AND_PORT = /^[\w.-]+:\d+$/;
|
|
14
|
+
const LEADING_PUNCTUATION = /* @__PURE__ */ new Set(["(", "'", '"', "`", "<", "[", "{"]);
|
|
15
|
+
const TRAILING_PUNCTUATION = /* @__PURE__ */ new Set([
|
|
16
|
+
")",
|
|
17
|
+
"'",
|
|
18
|
+
'"',
|
|
19
|
+
"`",
|
|
20
|
+
">",
|
|
21
|
+
"]",
|
|
22
|
+
"}",
|
|
23
|
+
",",
|
|
24
|
+
";",
|
|
25
|
+
".",
|
|
26
|
+
"!",
|
|
27
|
+
"?"
|
|
28
|
+
]);
|
|
29
|
+
function isSafeWord(word) {
|
|
30
|
+
if (!/[/\\@:]/.test(word) && !IPV4.test(word)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return SCOPED_PACKAGE.test(word) || KNOWN_SPECIFIER.test(word) || word.startsWith("npm:") && SCOPED_PACKAGE.test(word.slice(4)) || CLOCK_TIME.test(word);
|
|
34
|
+
}
|
|
35
|
+
function placeholderFor(word) {
|
|
36
|
+
if (word.includes("://")) {
|
|
37
|
+
return "[url]";
|
|
38
|
+
}
|
|
39
|
+
if (IPV4.test(word) || IPV6.test(word) || HOST_AND_PORT.test(word)) {
|
|
40
|
+
return "[host]";
|
|
41
|
+
}
|
|
42
|
+
if (/[/\\]/.test(word)) {
|
|
43
|
+
return "[path]";
|
|
44
|
+
}
|
|
45
|
+
return "[redacted]";
|
|
46
|
+
}
|
|
47
|
+
function redactWord(word) {
|
|
48
|
+
let start = 0;
|
|
49
|
+
let end = word.length;
|
|
50
|
+
while (end > start && TRAILING_PUNCTUATION.has(word[end - 1])) {
|
|
51
|
+
end--;
|
|
52
|
+
}
|
|
53
|
+
const withoutTrailing = word.slice(0, end);
|
|
54
|
+
if (!CLOCK_TIME.test(withoutTrailing) && IPV6.test(withoutTrailing)) {
|
|
55
|
+
return "[host]" + word.slice(end);
|
|
56
|
+
}
|
|
57
|
+
while (start < end && LEADING_PUNCTUATION.has(word[start])) {
|
|
58
|
+
start++;
|
|
59
|
+
}
|
|
60
|
+
const core = word.slice(start, end);
|
|
61
|
+
const checked = core.endsWith(":") ? core.slice(0, -1) : core;
|
|
62
|
+
if (checked === "" || isSafeWord(checked)) {
|
|
63
|
+
return word;
|
|
64
|
+
}
|
|
65
|
+
return word.slice(0, start) + placeholderFor(core) + word.slice(end);
|
|
66
|
+
}
|
|
67
|
+
function redactError(error) {
|
|
68
|
+
const redacted = new Error(redactErrorMessage(error));
|
|
69
|
+
if (error instanceof Error) {
|
|
70
|
+
redacted.name = error.name;
|
|
71
|
+
}
|
|
72
|
+
redacted.stack = `${redacted.name}: ${redacted.message}`;
|
|
73
|
+
return redacted;
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
redactError,
|
|
77
|
+
redactErrorMessage
|
|
78
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/cli-helpers",
|
|
3
|
-
"version": "8.0.0-canary.
|
|
3
|
+
"version": "8.0.0-canary.3595",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/cedarjs/cedar.git",
|
|
@@ -125,8 +125,8 @@
|
|
|
125
125
|
"test:watch": "vitest watch"
|
|
126
126
|
},
|
|
127
127
|
"dependencies": {
|
|
128
|
-
"@cedarjs/project-config": "8.0.0-canary.
|
|
129
|
-
"@cedarjs/telemetry": "8.0.0-canary.
|
|
128
|
+
"@cedarjs/project-config": "8.0.0-canary.3595",
|
|
129
|
+
"@cedarjs/telemetry": "8.0.0-canary.3595",
|
|
130
130
|
"@listr2/prompt-adapter-enquirer": "4.4.3",
|
|
131
131
|
"@opentelemetry/api": "1.9.1",
|
|
132
132
|
"ansis": "4.4.0",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"yargs-parser": "21.1.1"
|
|
147
147
|
},
|
|
148
148
|
"devDependencies": {
|
|
149
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
149
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3595",
|
|
150
150
|
"@types/dotenv-defaults": "^5.0.0",
|
|
151
151
|
"@types/lodash": "4.17.27",
|
|
152
152
|
"@types/pascalcase": "1.0.3",
|