@fluidframework/telemetry-utils 2.0.0-rc.2.0.2 → 2.0.0-rc.3.0.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/CHANGELOG.md +23 -0
- package/api-report/telemetry-utils.api.md +6 -6
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -2
- package/dist/config.js.map +1 -1
- package/dist/error.d.ts +4 -2
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +5 -5
- package/dist/error.js.map +1 -1
- package/dist/errorLogging.d.ts +3 -2
- package/dist/errorLogging.d.ts.map +1 -1
- package/dist/errorLogging.js.map +1 -1
- package/dist/eventEmitterWithErrorHandling.d.ts +1 -1
- package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/dist/eventEmitterWithErrorHandling.js +2 -2
- package/dist/eventEmitterWithErrorHandling.js.map +1 -1
- package/dist/events.d.ts +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/fluidErrorBase.d.ts.map +1 -1
- package/dist/fluidErrorBase.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +28 -0
- package/dist/logger.d.ts +2 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/mockLogger.d.ts +1 -1
- package/dist/mockLogger.d.ts.map +1 -1
- package/dist/mockLogger.js +3 -3
- package/dist/mockLogger.js.map +1 -1
- package/dist/public.d.ts +20 -0
- package/dist/sampledTelemetryHelper.d.ts +2 -2
- package/dist/sampledTelemetryHelper.d.ts.map +1 -1
- package/dist/sampledTelemetryHelper.js.map +1 -1
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -0
- package/dist/utils.js.map +1 -1
- package/internal.d.ts +11 -0
- package/legacy.d.ts +11 -0
- package/lib/config.d.ts +2 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +5 -1
- package/lib/config.js.map +1 -1
- package/lib/error.d.ts +4 -2
- package/lib/error.d.ts.map +1 -1
- package/lib/error.js +1 -1
- package/lib/error.js.map +1 -1
- package/lib/errorLogging.d.ts +3 -2
- package/lib/errorLogging.d.ts.map +1 -1
- package/lib/errorLogging.js.map +1 -1
- package/lib/eventEmitterWithErrorHandling.d.ts +1 -1
- package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/lib/eventEmitterWithErrorHandling.js.map +1 -1
- package/lib/events.d.ts +1 -1
- package/lib/events.d.ts.map +1 -1
- package/lib/events.js.map +1 -1
- package/lib/fluidErrorBase.d.ts.map +1 -1
- package/lib/fluidErrorBase.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +28 -0
- package/lib/logger.d.ts +2 -2
- package/lib/logger.d.ts.map +1 -1
- package/lib/logger.js +2 -2
- package/lib/logger.js.map +1 -1
- package/lib/mockLogger.d.ts +1 -1
- package/lib/mockLogger.d.ts.map +1 -1
- package/lib/mockLogger.js +2 -2
- package/lib/mockLogger.js.map +1 -1
- package/lib/public.d.ts +20 -0
- package/lib/sampledTelemetryHelper.d.ts +2 -2
- package/lib/sampledTelemetryHelper.d.ts.map +1 -1
- package/lib/sampledTelemetryHelper.js.map +1 -1
- package/lib/utils.d.ts +4 -0
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +4 -0
- package/lib/utils.js.map +1 -1
- package/package.json +27 -48
- package/src/config.ts +5 -3
- package/src/error.ts +4 -4
- package/src/errorLogging.ts +6 -8
- package/src/eventEmitterWithErrorHandling.ts +2 -1
- package/src/events.ts +2 -1
- package/src/fluidErrorBase.ts +1 -0
- package/src/index.ts +1 -0
- package/src/logger.ts +7 -6
- package/src/mockLogger.ts +4 -3
- package/src/sampledTelemetryHelper.ts +3 -2
- package/src/utils.ts +2 -0
- package/api-extractor-cjs.json +0 -8
- package/dist/telemetry-utils-alpha.d.ts +0 -282
- package/dist/telemetry-utils-beta.d.ts +0 -239
- package/dist/telemetry-utils-public.d.ts +0 -239
- package/dist/telemetry-utils-untrimmed.d.ts +0 -1077
- package/lib/telemetry-utils-alpha.d.ts +0 -282
- package/lib/telemetry-utils-beta.d.ts +0 -239
- package/lib/telemetry-utils-public.d.ts +0 -239
- package/lib/telemetry-utils-untrimmed.d.ts +0 -1077
- package/lib/test/EventEmitterWithErrorHandling.spec.js +0 -86
- package/lib/test/EventEmitterWithErrorHandling.spec.js.map +0 -1
- package/lib/test/childLogger.spec.js +0 -233
- package/lib/test/childLogger.spec.js.map +0 -1
- package/lib/test/config.spec.js +0 -229
- package/lib/test/config.spec.js.map +0 -1
- package/lib/test/error.spec.js +0 -161
- package/lib/test/error.spec.js.map +0 -1
- package/lib/test/errorLogging.spec.js +0 -801
- package/lib/test/errorLogging.spec.js.map +0 -1
- package/lib/test/errorTypeLoggingTest.spec.js +0 -107
- package/lib/test/errorTypeLoggingTest.spec.js.map +0 -1
- package/lib/test/mockLogger.spec.js +0 -164
- package/lib/test/mockLogger.spec.js.map +0 -1
- package/lib/test/multiSinkLogger.spec.js +0 -84
- package/lib/test/multiSinkLogger.spec.js.map +0 -1
- package/lib/test/performanceEvent.spec.js +0 -86
- package/lib/test/performanceEvent.spec.js.map +0 -1
- package/lib/test/sampledTelemetryHelper.spec.js +0 -169
- package/lib/test/sampledTelemetryHelper.spec.js.map +0 -1
- package/lib/test/telemetryLogger.spec.js +0 -357
- package/lib/test/telemetryLogger.spec.js.map +0 -1
- package/lib/test/thresholdCounter.spec.js +0 -51
- package/lib/test/thresholdCounter.spec.js.map +0 -1
- package/lib/test/types/validateTelemetryUtilsPrevious.generated.js +0 -132
- package/lib/test/types/validateTelemetryUtilsPrevious.generated.js.map +0 -1
- package/lib/test/utils.spec.js +0 -284
- package/lib/test/utils.spec.js.map +0 -1
- /package/{dist → lib}/tsdoc-metadata.json +0 -0
|
@@ -1,801 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
6
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
-
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
8
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
9
|
-
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
10
|
-
/* eslint-disable unicorn/consistent-function-scoping */
|
|
11
|
-
/* eslint-disable unicorn/no-null */
|
|
12
|
-
import { strict as assert } from "node:assert";
|
|
13
|
-
import sinon from "sinon";
|
|
14
|
-
import { v4 as uuid } from "uuid";
|
|
15
|
-
import { TelemetryDataTag, TelemetryLogger, TaggedLoggerAdapter } from "../logger.js";
|
|
16
|
-
import { LoggingError, isTaggedTelemetryPropertyValue, normalizeError, wrapError, wrapErrorAndLog, extractLogSafeErrorProperties, isExternalError, } from "../errorLogging.js";
|
|
17
|
-
import { hasErrorInstanceId, isFluidError, isValidLegacyError, } from "../fluidErrorBase.js";
|
|
18
|
-
import { MockLogger } from "../mockLogger.js";
|
|
19
|
-
describe("Error Logging", () => {
|
|
20
|
-
describe("TelemetryLogger.prepareErrorObject", () => {
|
|
21
|
-
function freshEvent() {
|
|
22
|
-
return { category: "cat1", eventName: "event1" };
|
|
23
|
-
}
|
|
24
|
-
function createILoggingError(props) {
|
|
25
|
-
return { ...props, getTelemetryProperties: () => props };
|
|
26
|
-
}
|
|
27
|
-
it("non-object error added to event", () => {
|
|
28
|
-
let event = freshEvent();
|
|
29
|
-
TelemetryLogger.prepareErrorObject(event, "hello", false);
|
|
30
|
-
assert.strictEqual(event.error, "hello", "string should work");
|
|
31
|
-
event = freshEvent();
|
|
32
|
-
TelemetryLogger.prepareErrorObject(event, 42, false);
|
|
33
|
-
assert.strictEqual(event.error, "42", "number should work");
|
|
34
|
-
event = freshEvent();
|
|
35
|
-
TelemetryLogger.prepareErrorObject(event, true, false);
|
|
36
|
-
assert.strictEqual(event.error, "true", "boolean should work");
|
|
37
|
-
event = freshEvent();
|
|
38
|
-
TelemetryLogger.prepareErrorObject(event, undefined, false);
|
|
39
|
-
assert.strictEqual(event.error, "undefined", "undefined should work");
|
|
40
|
-
// Technically this violates TelemetryBaseEventPropertyType's type constraint but it's actually supported
|
|
41
|
-
event = freshEvent();
|
|
42
|
-
TelemetryLogger.prepareErrorObject(event, null, false);
|
|
43
|
-
assert.strictEqual(event.error, "null", "null should work");
|
|
44
|
-
});
|
|
45
|
-
it("stack and message added to event (stack should exclude message)", () => {
|
|
46
|
-
const event = freshEvent();
|
|
47
|
-
const error = new Error("boom");
|
|
48
|
-
error.name = "MyErrorName";
|
|
49
|
-
TelemetryLogger.prepareErrorObject(event, error, false);
|
|
50
|
-
assert(event.error === "boom");
|
|
51
|
-
assert(event.stack.includes("MyErrorName"));
|
|
52
|
-
assert(!event.stack.includes("boom"));
|
|
53
|
-
});
|
|
54
|
-
it("containsPII (legacy) is ignored", () => {
|
|
55
|
-
// Previously, setting containsPII = true on an error obj would (attempt to) redact its message
|
|
56
|
-
const event = freshEvent();
|
|
57
|
-
const error = new Error("boom");
|
|
58
|
-
error.name = "MyErrorName";
|
|
59
|
-
error.containsPII = true;
|
|
60
|
-
TelemetryLogger.prepareErrorObject(event, error, false);
|
|
61
|
-
assert(event.error === "boom");
|
|
62
|
-
assert(event.stack.includes("MyErrorName"));
|
|
63
|
-
});
|
|
64
|
-
it("getTelemetryProperties - tags on overwritten Error base props", () => {
|
|
65
|
-
const event = freshEvent();
|
|
66
|
-
const error = createILoggingError({
|
|
67
|
-
message: { value: "Mark Fields", tag: "UserData" },
|
|
68
|
-
stack: { value: "tagged", tag: TelemetryDataTag.CodeArtifact },
|
|
69
|
-
});
|
|
70
|
-
TelemetryLogger.prepareErrorObject(event, error, false);
|
|
71
|
-
assert.deepStrictEqual(event.message, { value: "Mark Fields", tag: "UserData" });
|
|
72
|
-
assert.deepStrictEqual(event.error, "[object Object]"); // weird but ok
|
|
73
|
-
assert.deepStrictEqual(event.stack, {
|
|
74
|
-
value: "tagged",
|
|
75
|
-
tag: TelemetryDataTag.CodeArtifact,
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
it("getTelemetryProperties absent - no further props added", () => {
|
|
79
|
-
const event = freshEvent();
|
|
80
|
-
const error = { ...new Error("boom"), foo: "foo", bar: 2 };
|
|
81
|
-
TelemetryLogger.prepareErrorObject(event, error, false);
|
|
82
|
-
assert(event.foo === undefined && event.bar === undefined);
|
|
83
|
-
});
|
|
84
|
-
it("getTelemetryProperties overlaps event - do not overwrite", () => {
|
|
85
|
-
const event = { ...freshEvent(), foo: "event_foo", bar: 42 };
|
|
86
|
-
const error = createILoggingError({ foo: "error_foo", bar: -1 });
|
|
87
|
-
TelemetryLogger.prepareErrorObject(event, error, false);
|
|
88
|
-
assert(event.foo === "event_foo" && event.bar === 42);
|
|
89
|
-
});
|
|
90
|
-
it("getTelemetryProperties present - add additional props", () => {
|
|
91
|
-
const event = freshEvent();
|
|
92
|
-
const error = createILoggingError({ foo: "foo", bar: 2 });
|
|
93
|
-
TelemetryLogger.prepareErrorObject(event, error, false);
|
|
94
|
-
assert(event.foo === "foo" && event.bar === 2);
|
|
95
|
-
});
|
|
96
|
-
it("fetchStack false - Don't add a stack if missing", () => {
|
|
97
|
-
const event = freshEvent();
|
|
98
|
-
const error = { message: "I have no stack" };
|
|
99
|
-
TelemetryLogger.prepareErrorObject(event, error, false);
|
|
100
|
-
assert.strictEqual(event.stack, undefined);
|
|
101
|
-
});
|
|
102
|
-
it("fetchStack true - Don't add a stack if present", () => {
|
|
103
|
-
const event = freshEvent();
|
|
104
|
-
const error = new Error("boom");
|
|
105
|
-
error.name = "MyName";
|
|
106
|
-
TelemetryLogger.prepareErrorObject(event, error, false);
|
|
107
|
-
assert(event.stack.includes("MyName"));
|
|
108
|
-
});
|
|
109
|
-
it("fetchStack true - Add a stack if missing", () => {
|
|
110
|
-
const event = freshEvent();
|
|
111
|
-
const error = { message: "I have no stack - boom", name: "MyName" };
|
|
112
|
-
TelemetryLogger.prepareErrorObject(event, error, true);
|
|
113
|
-
assert.strictEqual(typeof event.stack, "string");
|
|
114
|
-
assert(!event.stack.includes("MyName"));
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
describe("TaggedLoggerAdapter", () => {
|
|
118
|
-
const events = [];
|
|
119
|
-
class TestTelemetryLogger extends TelemetryLogger {
|
|
120
|
-
constructor() {
|
|
121
|
-
super(...arguments);
|
|
122
|
-
this.events = [];
|
|
123
|
-
}
|
|
124
|
-
send(event) {
|
|
125
|
-
events.push(this.prepareEvent(event));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const adaptedLogger = new TaggedLoggerAdapter(new TestTelemetryLogger("namespace"));
|
|
129
|
-
it("TaggedLoggerAdapter - tagged UserData is removed", () => {
|
|
130
|
-
const event = {
|
|
131
|
-
category: "cat",
|
|
132
|
-
eventName: "event",
|
|
133
|
-
userDataObject: {
|
|
134
|
-
tag: TelemetryDataTag.UserData,
|
|
135
|
-
value: "someUserData",
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
adaptedLogger.send(event);
|
|
139
|
-
assert.strictEqual(events[0].userDataObject, "REDACTED (UserData)", "someUserData should be redacted");
|
|
140
|
-
events.pop();
|
|
141
|
-
});
|
|
142
|
-
it("TaggedLoggerAdapter - tagged CodeArtifact are preserved", () => {
|
|
143
|
-
const event = {
|
|
144
|
-
category: "cat",
|
|
145
|
-
eventName: "event",
|
|
146
|
-
packageDataObject: {
|
|
147
|
-
tag: TelemetryDataTag.CodeArtifact,
|
|
148
|
-
value: "somePackageData",
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
adaptedLogger.send(event);
|
|
152
|
-
assert.strictEqual(events[0].packageDataObject, "somePackageData", "somePackageData should be preserved");
|
|
153
|
-
events.pop();
|
|
154
|
-
});
|
|
155
|
-
it("TaggedLoggerAdapter - tagged [unrecognized tag] are removed", () => {
|
|
156
|
-
const event = {
|
|
157
|
-
category: "cat",
|
|
158
|
-
eventName: "event",
|
|
159
|
-
unknownTaggedObject: {
|
|
160
|
-
tag: "someUnknownTag",
|
|
161
|
-
value: "someEvilData",
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
adaptedLogger.send(event);
|
|
165
|
-
assert.strictEqual(events[0].unknownTaggedObject, "REDACTED (unknown tag)", "someUnknownTag should be redacted");
|
|
166
|
-
events.pop();
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
describe("TaggedTelemetryData", () => {
|
|
170
|
-
it("Ensure backwards compatibility", () => {
|
|
171
|
-
// The values of the enum should never change (even if the keys are renamed)
|
|
172
|
-
assert(TelemetryDataTag.CodeArtifact === "CodeArtifact");
|
|
173
|
-
assert(TelemetryDataTag.UserData === "UserData");
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
describe("isTaggedTelemetryPropertyValue", () => {
|
|
177
|
-
it("non-object input not ok", () => {
|
|
178
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue("hello"), false);
|
|
179
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue(123), false);
|
|
180
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue(false), false);
|
|
181
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue(undefined), false);
|
|
182
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue(null), false);
|
|
183
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue(function x() {
|
|
184
|
-
return 54;
|
|
185
|
-
}), false);
|
|
186
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue(Symbol("okay")), false);
|
|
187
|
-
});
|
|
188
|
-
it("non-object value ok", () => {
|
|
189
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: "hello", tag: "any string" }), true);
|
|
190
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: 123, tag: "any string" }), true);
|
|
191
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: false, tag: "any string" }), true);
|
|
192
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: undefined, tag: "any string" }), true);
|
|
193
|
-
});
|
|
194
|
-
it("Check result for various invalid inputs (per typings)", () => {
|
|
195
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ tag: "any string" }), true, "value prop may be absent");
|
|
196
|
-
// The type guard used is a bit imprecise. Here is proof (these "shouldn't" be ok)
|
|
197
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({
|
|
198
|
-
value: function x() {
|
|
199
|
-
return 54;
|
|
200
|
-
},
|
|
201
|
-
tag: "any string",
|
|
202
|
-
}), true);
|
|
203
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: Symbol("okay"), tag: "any string" }), true);
|
|
204
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: "hello", tag: 1 }), false, "number tag is bad");
|
|
205
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: "hello", tag: false }), false, "boolean tag is bad");
|
|
206
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: "hello", tag: {} }), false, "object tag is bad");
|
|
207
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: "hello", tag: null }), false, "null tag is bad");
|
|
208
|
-
assert.strictEqual(isTaggedTelemetryPropertyValue({ value: "hello" }), false, "undefined (missing) tag is bad");
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
describe("LoggingError", () => {
|
|
212
|
-
it("ctor props are assigned to the object", () => {
|
|
213
|
-
const loggingError = new LoggingError("myMessage", {
|
|
214
|
-
p1: 1,
|
|
215
|
-
p2: "two",
|
|
216
|
-
p3: true,
|
|
217
|
-
tagged: { value: 4, tag: "CodeArtifact" },
|
|
218
|
-
});
|
|
219
|
-
const errorAsAny = loggingError;
|
|
220
|
-
assert.strictEqual(errorAsAny.message, "myMessage");
|
|
221
|
-
assert.strictEqual(errorAsAny.p1, 1);
|
|
222
|
-
assert.strictEqual(errorAsAny.p2, "two");
|
|
223
|
-
assert.strictEqual(errorAsAny.p3, true);
|
|
224
|
-
assert.deepStrictEqual(errorAsAny.tagged, { value: 4, tag: "CodeArtifact" });
|
|
225
|
-
});
|
|
226
|
-
it("errorInstanceId unique each time", () => {
|
|
227
|
-
const e1 = new LoggingError("1");
|
|
228
|
-
const e2 = new LoggingError("2");
|
|
229
|
-
assert.equal(e1.errorInstanceId.length, 36, "should be guid-length");
|
|
230
|
-
assert.equal(e2.errorInstanceId.length, 36, "should be guid-length");
|
|
231
|
-
assert.notEqual(e1.errorInstanceId, e2.errorInstanceId, "each error instance should get unique id");
|
|
232
|
-
});
|
|
233
|
-
it("getTelemetryProperties extracts all untagged ctor props", () => {
|
|
234
|
-
const loggingError = new LoggingError("myMessage", { p1: 1, p2: "two", p3: true });
|
|
235
|
-
const props = loggingError.getTelemetryProperties();
|
|
236
|
-
assert.strictEqual(props.message, "myMessage");
|
|
237
|
-
assert.strictEqual(typeof props.stack, "string");
|
|
238
|
-
assert.strictEqual(props.name, undefined); // Error.name is not logged
|
|
239
|
-
assert.strictEqual(props.p1, 1);
|
|
240
|
-
assert.strictEqual(props.p2, "two");
|
|
241
|
-
assert.strictEqual(props.p3, true);
|
|
242
|
-
});
|
|
243
|
-
it("getTelemetryProperties respects omitPropsFromLogging", () => {
|
|
244
|
-
const loggingError = new LoggingError("myMessage", {}, new Set(["foo"]));
|
|
245
|
-
loggingError.foo = "secrets!";
|
|
246
|
-
loggingError.bar = "normal";
|
|
247
|
-
const props = loggingError.getTelemetryProperties();
|
|
248
|
-
assert.strictEqual(props.omitPropsFromLogging, undefined, "omitPropsFromLogging itself should be omitted");
|
|
249
|
-
assert.strictEqual(props.foo, undefined, "foo should have been omitted");
|
|
250
|
-
assert.strictEqual(props.bar, "normal", "bar should not be omitted");
|
|
251
|
-
});
|
|
252
|
-
it("addTelemetryProperties - adds to object, returned from getTelemetryProperties, doesn't overwrite", () => {
|
|
253
|
-
const loggingError = new LoggingError("myMessage", { p1: 1, p2: "two", p3: true });
|
|
254
|
-
loggingError.p1 = "should not be overwritten";
|
|
255
|
-
loggingError.addTelemetryProperties({
|
|
256
|
-
p1: "ignored",
|
|
257
|
-
p4: 4,
|
|
258
|
-
p5: true,
|
|
259
|
-
p6: { value: 5, tag: "CodeArtifact" },
|
|
260
|
-
p7: ["a", "b", "c"],
|
|
261
|
-
p8: [1, 2, 3],
|
|
262
|
-
p9: [true, true, false],
|
|
263
|
-
p10: { one: "1" },
|
|
264
|
-
p11: undefined,
|
|
265
|
-
p12: { value: ["1", 2, true], tag: "CodeArtifact" },
|
|
266
|
-
});
|
|
267
|
-
const props = loggingError.getTelemetryProperties();
|
|
268
|
-
assert.strictEqual(props.p1, "should not be overwritten");
|
|
269
|
-
assert.strictEqual(props.p4, 4);
|
|
270
|
-
assert.strictEqual(props.p5, true);
|
|
271
|
-
assert.deepStrictEqual(props.p6, { value: 5, tag: "CodeArtifact" });
|
|
272
|
-
assert.strictEqual(props.p7, '["a","b","c"]');
|
|
273
|
-
assert.strictEqual(props.p8, "[1,2,3]");
|
|
274
|
-
assert.strictEqual(props.p9, "[true,true,false]");
|
|
275
|
-
assert.strictEqual(props.p10, `{"one":"1"}`);
|
|
276
|
-
assert.strictEqual(props.p11, undefined);
|
|
277
|
-
assert.deepStrictEqual(props.p12, { value: `["1",2,true]`, tag: "CodeArtifact" });
|
|
278
|
-
const errorAsAny = loggingError;
|
|
279
|
-
assert.strictEqual(errorAsAny.p1, "should not be overwritten");
|
|
280
|
-
assert.strictEqual(errorAsAny.p4, 4);
|
|
281
|
-
assert.strictEqual(errorAsAny.p5, true);
|
|
282
|
-
assert.deepStrictEqual(errorAsAny.p6, { value: 5, tag: "CodeArtifact" });
|
|
283
|
-
assert.deepStrictEqual(errorAsAny.p7, ["a", "b", "c"]);
|
|
284
|
-
assert.deepStrictEqual(errorAsAny.p8, [1, 2, 3]);
|
|
285
|
-
assert.deepStrictEqual(errorAsAny.p9, [true, true, false]);
|
|
286
|
-
assert.deepStrictEqual(errorAsAny.p10, { one: "1" });
|
|
287
|
-
assert.strictEqual(errorAsAny.p11, undefined);
|
|
288
|
-
assert.deepStrictEqual(errorAsAny.p12, { value: ["1", 2, true], tag: "CodeArtifact" });
|
|
289
|
-
});
|
|
290
|
-
it("Set valid props via 'as any' - returned from getTelemetryProperties, overwrites", () => {
|
|
291
|
-
const loggingError = new LoggingError("myMessage", { p1: 1, p2: "two", p3: true });
|
|
292
|
-
loggingError.addTelemetryProperties({ p1: "should be overwritten" });
|
|
293
|
-
const errorAsAny = loggingError;
|
|
294
|
-
// Things that could be set with addTelemetryProperties
|
|
295
|
-
errorAsAny.p1 = "one";
|
|
296
|
-
errorAsAny.p4 = 4;
|
|
297
|
-
errorAsAny.p5 = true;
|
|
298
|
-
errorAsAny.p6 = { value: 5, tag: "CodeArtifact" };
|
|
299
|
-
errorAsAny.userData6 = { value: 5, tag: "UserData" };
|
|
300
|
-
errorAsAny.p7 = ["a", "b", "c"];
|
|
301
|
-
errorAsAny.p8 = [1, 2, 3];
|
|
302
|
-
errorAsAny.p9 = [true, true, false];
|
|
303
|
-
errorAsAny.p10 = { one: "1" };
|
|
304
|
-
errorAsAny.p11 = undefined;
|
|
305
|
-
errorAsAny.p12 = { value: ["1", 2, true], tag: "CodeArtifact" };
|
|
306
|
-
// Things that can't be set with addTelemetryProperties
|
|
307
|
-
errorAsAny.p13 = null; // Null
|
|
308
|
-
errorAsAny.p14 = ["a", "b", "c", null]; // Array with nulls
|
|
309
|
-
errorAsAny.p15 = [[1, 2]]; // Nested array
|
|
310
|
-
const props = loggingError.getTelemetryProperties();
|
|
311
|
-
assert.strictEqual(props.p1, "one");
|
|
312
|
-
assert.strictEqual(props.p4, 4);
|
|
313
|
-
assert.strictEqual(props.p5, true);
|
|
314
|
-
assert.deepStrictEqual(props.p6, { value: 5, tag: "CodeArtifact" });
|
|
315
|
-
assert.deepStrictEqual(props.userData6, { value: 5, tag: "UserData" });
|
|
316
|
-
assert.strictEqual(props.p7, `["a","b","c"]`);
|
|
317
|
-
assert.strictEqual(props.p8, `[1,2,3]`);
|
|
318
|
-
assert.strictEqual(props.p9, `[true,true,false]`);
|
|
319
|
-
assert.strictEqual(props.p10, `{"one":"1"}`);
|
|
320
|
-
assert.strictEqual(props.p11, undefined);
|
|
321
|
-
assert.deepStrictEqual(props.p12, { value: `["1",2,true]`, tag: "CodeArtifact" });
|
|
322
|
-
assert.strictEqual(props.p13, "null");
|
|
323
|
-
assert.strictEqual(props.p14, `["a","b","c",null]`);
|
|
324
|
-
assert.strictEqual(props.p15, "[[1,2]]");
|
|
325
|
-
});
|
|
326
|
-
it("addTelemetryProperties - Does not overwrite base class Error fields (untagged)", () => {
|
|
327
|
-
const loggingError = new LoggingError("myMessage");
|
|
328
|
-
const propsWillBeIgnored = { message: "surprise1", stack: "surprise2" };
|
|
329
|
-
loggingError.addTelemetryProperties(propsWillBeIgnored);
|
|
330
|
-
const props = loggingError.getTelemetryProperties();
|
|
331
|
-
delete props.fluidErrorCode; // It's on there for back compat, not trying to test it here
|
|
332
|
-
const { message, stack, errorInstanceId } = loggingError;
|
|
333
|
-
assert.deepStrictEqual(props, { message, stack, errorInstanceId }, "addTelemetryProperties should not overwrite existing props");
|
|
334
|
-
});
|
|
335
|
-
it("addTelemetryProperties - Does not overwrite base class Error fields (tagged)", () => {
|
|
336
|
-
const loggingError = new LoggingError("myMessage");
|
|
337
|
-
const propsWillBeIgnored = {
|
|
338
|
-
message: { value: "Mark Fields", tag: "UserData" },
|
|
339
|
-
stack: { value: "surprise2", tag: "CodeArtifact" },
|
|
340
|
-
};
|
|
341
|
-
loggingError.addTelemetryProperties(propsWillBeIgnored);
|
|
342
|
-
const props = loggingError.getTelemetryProperties();
|
|
343
|
-
delete props.fluidErrorCode; // It's on there for back compat, not trying to test it here
|
|
344
|
-
const { message, stack, errorInstanceId } = loggingError;
|
|
345
|
-
assert.deepStrictEqual(props, { message, stack, errorInstanceId }, "addTelemetryProperties should not overwrite existing props");
|
|
346
|
-
});
|
|
347
|
-
it("addTelemetryProperties - Does not overwrite existing telemetry props", () => {
|
|
348
|
-
const loggingError = new LoggingError("myMessage", { p1: 1 });
|
|
349
|
-
loggingError.addTelemetryProperties({ p1: "one" });
|
|
350
|
-
assert(loggingError.getTelemetryProperties().p1 === 1);
|
|
351
|
-
loggingError.addTelemetryProperties({ p1: "uno" });
|
|
352
|
-
assert(loggingError.getTelemetryProperties().p1 === 1);
|
|
353
|
-
});
|
|
354
|
-
it("typeCheck - Correctly type checks an instace of LoggingError", () => {
|
|
355
|
-
const loggingError = new LoggingError("myMessage", { p1: 1 });
|
|
356
|
-
const normalizedLoggingError = normalizeError(loggingError);
|
|
357
|
-
const basicError = new Error("basicErrorMessage");
|
|
358
|
-
assert.strictEqual(LoggingError.typeCheck(loggingError), true, "LoggingError is a LoggingError");
|
|
359
|
-
assert.strictEqual(LoggingError.typeCheck(normalizedLoggingError), true, "Normalized Error is a LoggingError");
|
|
360
|
-
assert.strictEqual(LoggingError.typeCheck(basicError), false, "Error is not a LoggingError");
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
describe("extractLogSafeErrorProperties", () => {
|
|
364
|
-
function createSampleError() {
|
|
365
|
-
try {
|
|
366
|
-
const error = new Error("asdf");
|
|
367
|
-
error.name = "FooError";
|
|
368
|
-
throw error;
|
|
369
|
-
}
|
|
370
|
-
catch (error) {
|
|
371
|
-
return error;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
it("non-object error yields correct message", () => {
|
|
375
|
-
assert.strictEqual(extractLogSafeErrorProperties("hello", false /* sanitizeStack */).message, "hello");
|
|
376
|
-
assert.strictEqual(extractLogSafeErrorProperties(42, false /* sanitizeStack */).message, "42");
|
|
377
|
-
assert.strictEqual(extractLogSafeErrorProperties(true, false /* sanitizeStack */).message, "true");
|
|
378
|
-
assert.strictEqual(extractLogSafeErrorProperties(undefined, false /* sanitizeStack */).message, "undefined");
|
|
379
|
-
});
|
|
380
|
-
it("object error yields correct message", () => {
|
|
381
|
-
assert.strictEqual(extractLogSafeErrorProperties({ message: "hello" }, false /* sanitizeStack */)
|
|
382
|
-
.message, "hello");
|
|
383
|
-
assert.strictEqual(extractLogSafeErrorProperties({ message: 42 }, false /* sanitizeStack */).message, "[object Object]");
|
|
384
|
-
assert.strictEqual(extractLogSafeErrorProperties({ foo: 42 }, false /* sanitizeStack */).message, "[object Object]");
|
|
385
|
-
assert.strictEqual(extractLogSafeErrorProperties([1, 2, 3], false /* sanitizeStack */).message, "1,2,3");
|
|
386
|
-
assert.strictEqual(extractLogSafeErrorProperties(null, false /* sanitizeStack */).message, "null");
|
|
387
|
-
});
|
|
388
|
-
it("extract errorType", () => {
|
|
389
|
-
assert.strictEqual(extractLogSafeErrorProperties({ errorType: "hello" }, false /* sanitizeStack */)
|
|
390
|
-
.errorType, "hello");
|
|
391
|
-
assert.strictEqual(extractLogSafeErrorProperties({ foo: "hello" }, false /* sanitizeStack */)
|
|
392
|
-
.errorType, undefined);
|
|
393
|
-
assert.strictEqual(extractLogSafeErrorProperties({ errorType: 42 }, false /* sanitizeStack */)
|
|
394
|
-
.errorType, undefined);
|
|
395
|
-
assert.strictEqual(extractLogSafeErrorProperties(42, false /* sanitizeStack */).errorType, undefined);
|
|
396
|
-
});
|
|
397
|
-
it("extract stack", () => {
|
|
398
|
-
const e1 = createSampleError();
|
|
399
|
-
const stack = extractLogSafeErrorProperties(e1, false /* sanitizeStack */).stack;
|
|
400
|
-
assert(typeof stack === "string");
|
|
401
|
-
assert(stack?.includes("asdf"), "stack is expected to contain the message");
|
|
402
|
-
assert(stack?.includes("FooError"), "stack is expected to contain the name");
|
|
403
|
-
const sanitizedStack = extractLogSafeErrorProperties(e1, true /* sanitizeStack */).stack;
|
|
404
|
-
assert(typeof sanitizedStack === "string");
|
|
405
|
-
assert(!sanitizedStack?.includes("asdf"), "message should have been removed from sanitized stack");
|
|
406
|
-
assert(sanitizedStack?.includes("FooError"), "name should still be in the sanitized stack");
|
|
407
|
-
});
|
|
408
|
-
it("extract stack non-standard values", () => {
|
|
409
|
-
// sanitizeStack true
|
|
410
|
-
assert.strictEqual(extractLogSafeErrorProperties({ stack: "hello" }, true /* sanitizeStack */).stack, "");
|
|
411
|
-
assert.strictEqual(extractLogSafeErrorProperties({ stack: "hello", name: "name" }, true /* sanitizeStack */).stack, "name");
|
|
412
|
-
// sanitizeStack false
|
|
413
|
-
assert.strictEqual(extractLogSafeErrorProperties({ stack: "hello" }, false /* sanitizeStack */).stack, "hello");
|
|
414
|
-
assert.strictEqual(extractLogSafeErrorProperties({ stack: "hello", name: "name" }, false /* sanitizeStack */).stack, "hello");
|
|
415
|
-
assert.strictEqual(extractLogSafeErrorProperties({ foo: "hello" }, false /* sanitizeStack */).stack, undefined);
|
|
416
|
-
assert.strictEqual(extractLogSafeErrorProperties({ stack: 42 }, false /* sanitizeStack */).stack, undefined);
|
|
417
|
-
assert.strictEqual(extractLogSafeErrorProperties(42, false /* sanitizeStack */).stack, undefined);
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
describe("normalizeError", () => {
|
|
421
|
-
describe("preserves properties", () => {
|
|
422
|
-
it("missing properties are not set", () => {
|
|
423
|
-
// eslint-disable-next-line unicorn/error-message
|
|
424
|
-
const unknownError = new Error();
|
|
425
|
-
const newError = normalizeError(unknownError);
|
|
426
|
-
assert.strictEqual(newError.canRetry, undefined, "canRetry not undefined");
|
|
427
|
-
assert.strictEqual(newError.retryAfterSeconds, undefined, "retryAfterSeconds not undefined");
|
|
428
|
-
});
|
|
429
|
-
it("existing retry properties are present in normalized error", () => {
|
|
430
|
-
const unknownError =
|
|
431
|
-
// eslint-disable-next-line unicorn/error-message
|
|
432
|
-
new Error();
|
|
433
|
-
unknownError.canRetry = true;
|
|
434
|
-
unknownError.retryAfterSeconds = 100;
|
|
435
|
-
const newError = normalizeError(unknownError);
|
|
436
|
-
assert.strictEqual(newError.canRetry, true, "canRetry not true");
|
|
437
|
-
assert.strictEqual(newError.retryAfterSeconds, 100, "retryAfterSeconds not 100");
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
});
|
|
442
|
-
class TestFluidError {
|
|
443
|
-
constructor(errorProps) {
|
|
444
|
-
this.name = "Error";
|
|
445
|
-
this.errorType = errorProps.errorType;
|
|
446
|
-
this.message = errorProps.message;
|
|
447
|
-
this.stack = errorProps.stack;
|
|
448
|
-
this.errorInstanceId = uuid();
|
|
449
|
-
this.atpStub = sinon.stub(this, "addTelemetryProperties");
|
|
450
|
-
this.gtpSpy = sinon.spy(this, "getTelemetryProperties");
|
|
451
|
-
this.expectedTelemetryProps = { ...errorProps };
|
|
452
|
-
}
|
|
453
|
-
getTelemetryProperties() {
|
|
454
|
-
// Don't actually return any props. We'll use the spy to ensure it was called
|
|
455
|
-
return {};
|
|
456
|
-
}
|
|
457
|
-
addTelemetryProperties(props) {
|
|
458
|
-
throw new Error("Not Implemented - Expected to be Stubbed via Sinon");
|
|
459
|
-
}
|
|
460
|
-
withoutProperty(propName) {
|
|
461
|
-
const objectWithoutProp = {};
|
|
462
|
-
objectWithoutProp[propName] = undefined;
|
|
463
|
-
Object.assign(this, objectWithoutProp);
|
|
464
|
-
return this;
|
|
465
|
-
}
|
|
466
|
-
withExpectedTelemetryProps(props) {
|
|
467
|
-
Object.assign(this.expectedTelemetryProps, props);
|
|
468
|
-
return this;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
const annotationCases = {
|
|
472
|
-
noAnnotations: {},
|
|
473
|
-
justProps: { props: { foo: "bar", one: 1, u: undefined, t: true } },
|
|
474
|
-
};
|
|
475
|
-
describe("normalizeError", () => {
|
|
476
|
-
describe("Valid Errors (Legacy and Current)", () => {
|
|
477
|
-
for (const annotationCase of Object.keys(annotationCases)) {
|
|
478
|
-
const annotations = annotationCases[annotationCase];
|
|
479
|
-
it(`Valid legacy error - Patch and return (annotations: ${annotationCase})`, () => {
|
|
480
|
-
// Arrange
|
|
481
|
-
const errorProps = { errorType: "et1", message: "m1" };
|
|
482
|
-
const legacyError = new TestFluidError(errorProps).withoutProperty("errorInstanceId");
|
|
483
|
-
// Act
|
|
484
|
-
const normalizedError = normalizeError(legacyError, annotations);
|
|
485
|
-
// Assert
|
|
486
|
-
assert.equal(normalizedError, legacyError, "normalize should yield the same error as passed in");
|
|
487
|
-
assert.equal(normalizedError.errorType, "et1", "errorType should be unchanged");
|
|
488
|
-
assert.equal(normalizedError.message, "m1", "message should be unchanged");
|
|
489
|
-
assert.equal(normalizedError.errorInstanceId.length, 36, "should be guid-length");
|
|
490
|
-
if (annotations.props !== undefined) {
|
|
491
|
-
assert(legacyError.atpStub.calledWith(annotations.props), "addTelemetryProperties should have been called");
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
it(`Valid Fluid Error - untouched (annotations: ${annotationCase})`, () => {
|
|
495
|
-
// Arrange
|
|
496
|
-
const fluidError = new TestFluidError({ errorType: "et1", message: "m1" });
|
|
497
|
-
// We don't expect legacyError to be modified itself at all
|
|
498
|
-
Object.freeze(fluidError);
|
|
499
|
-
// Act
|
|
500
|
-
const normalizedError = normalizeError(fluidError, annotations);
|
|
501
|
-
// Assert
|
|
502
|
-
assert(normalizedError === fluidError);
|
|
503
|
-
if (annotations.props !== undefined) {
|
|
504
|
-
assert(fluidError.atpStub.calledWith(annotations.props), "addTelemetryProperties should have been called");
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
it("Valid Fluid Error - stack not added if missing", () => {
|
|
509
|
-
// Arrange
|
|
510
|
-
const fluidError = new TestFluidError({
|
|
511
|
-
errorType: "et1",
|
|
512
|
-
message: "m1",
|
|
513
|
-
}).withoutProperty("stack");
|
|
514
|
-
// We don't expect legacyError to be modified itself at all
|
|
515
|
-
Object.freeze(fluidError);
|
|
516
|
-
// Act
|
|
517
|
-
const normalizedError = normalizeError(fluidError, {});
|
|
518
|
-
// Assert
|
|
519
|
-
assert(normalizedError === fluidError);
|
|
520
|
-
assert(normalizedError.stack === undefined);
|
|
521
|
-
});
|
|
522
|
-
it("Frozen legacy error - Throws", () => {
|
|
523
|
-
// Arrange
|
|
524
|
-
const errorProps = { errorType: "et1", message: "m1" };
|
|
525
|
-
const legacyError = new TestFluidError(errorProps).withoutProperty("errorInstanceId");
|
|
526
|
-
Object.freeze(legacyError);
|
|
527
|
-
// Act/Assert
|
|
528
|
-
assert.throws(() => normalizeError(legacyError, {}), /Cannot assign to read only property/);
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
describe("Errors Needing Normalization", () => {
|
|
532
|
-
class NamedError extends Error {
|
|
533
|
-
constructor() {
|
|
534
|
-
super(...arguments);
|
|
535
|
-
this.name = "CoolErrorName";
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
const sampleFluidError = () => new TestFluidError({
|
|
539
|
-
errorType: "someType",
|
|
540
|
-
message: "Hello",
|
|
541
|
-
stack: "cool stack trace",
|
|
542
|
-
});
|
|
543
|
-
const typicalOutput = (message, stackHint) => new TestFluidError({
|
|
544
|
-
errorType: "genericError",
|
|
545
|
-
message,
|
|
546
|
-
stack: stackHint,
|
|
547
|
-
});
|
|
548
|
-
const testCases = {
|
|
549
|
-
"Fluid Error minus errorType": () => ({
|
|
550
|
-
input: sampleFluidError().withoutProperty("errorType"),
|
|
551
|
-
expectedOutput: typicalOutput("Hello", "<<stack from input>>"),
|
|
552
|
-
}),
|
|
553
|
-
"Fluid Error minus message": () => ({
|
|
554
|
-
input: sampleFluidError().withoutProperty("message"),
|
|
555
|
-
expectedOutput: typicalOutput("[object Object]", "<<stack from input>>"),
|
|
556
|
-
}),
|
|
557
|
-
"Fluid Error minus getTelemetryProperties": () => ({
|
|
558
|
-
input: sampleFluidError().withoutProperty("getTelemetryProperties"),
|
|
559
|
-
expectedOutput: typicalOutput("Hello", "<<stack from input>>"),
|
|
560
|
-
}),
|
|
561
|
-
"Fluid Error minus addTelemetryProperties": () => ({
|
|
562
|
-
input: sampleFluidError().withoutProperty("addTelemetryProperties"),
|
|
563
|
-
expectedOutput: typicalOutput("Hello", "<<stack from input>>"),
|
|
564
|
-
}),
|
|
565
|
-
"Fluid Error minus errorType (no stack)": () => ({
|
|
566
|
-
input: sampleFluidError().withoutProperty("errorType").withoutProperty("stack"),
|
|
567
|
-
expectedOutput: typicalOutput("Hello", "<<natural stack>>"),
|
|
568
|
-
}),
|
|
569
|
-
"Fluid Error minus message (no stack)": () => ({
|
|
570
|
-
input: sampleFluidError().withoutProperty("message").withoutProperty("stack"),
|
|
571
|
-
expectedOutput: typicalOutput("[object Object]", "<<natural stack>>"),
|
|
572
|
-
}),
|
|
573
|
-
"Error object": () => ({
|
|
574
|
-
input: new NamedError("boom"),
|
|
575
|
-
expectedOutput: typicalOutput("boom", "<<stack from input>>").withExpectedTelemetryProps({ untrustedOrigin: 1 }),
|
|
576
|
-
}),
|
|
577
|
-
"LoggingError": () => ({
|
|
578
|
-
input: new LoggingError("boom"),
|
|
579
|
-
expectedOutput: typicalOutput("boom", "<<stack from input>>"),
|
|
580
|
-
}),
|
|
581
|
-
"Empty object": () => ({
|
|
582
|
-
input: {},
|
|
583
|
-
expectedOutput: typicalOutput("[object Object]", "<<natural stack>>").withExpectedTelemetryProps({ untrustedOrigin: 1 }),
|
|
584
|
-
}),
|
|
585
|
-
"object with stack": () => ({
|
|
586
|
-
input: { message: "whatever", stack: "fake stack goes here" },
|
|
587
|
-
expectedOutput: typicalOutput("whatever", "<<stack from input>>").withExpectedTelemetryProps({ untrustedOrigin: 1 }),
|
|
588
|
-
}),
|
|
589
|
-
"object with non-string message and name": () => ({
|
|
590
|
-
input: { message: 42, name: true },
|
|
591
|
-
expectedOutput: typicalOutput("[object Object]", "<<natural stack>>").withExpectedTelemetryProps({ untrustedOrigin: 1 }),
|
|
592
|
-
}),
|
|
593
|
-
"nullValue": () => ({
|
|
594
|
-
input: null,
|
|
595
|
-
expectedOutput: typicalOutput("null", "<<natural stack>>").withExpectedTelemetryProps({ untrustedOrigin: 1 }),
|
|
596
|
-
}),
|
|
597
|
-
"undef": () => ({
|
|
598
|
-
input: undefined,
|
|
599
|
-
expectedOutput: typicalOutput("undefined", "<<natural stack>>").withExpectedTelemetryProps({ typeofError: "undefined", untrustedOrigin: 1 }),
|
|
600
|
-
}),
|
|
601
|
-
"false": () => ({
|
|
602
|
-
input: false,
|
|
603
|
-
expectedOutput: typicalOutput("false", "<<natural stack>>").withExpectedTelemetryProps({ typeofError: "boolean", untrustedOrigin: 1 }),
|
|
604
|
-
}),
|
|
605
|
-
"true": () => ({
|
|
606
|
-
input: true,
|
|
607
|
-
expectedOutput: typicalOutput("true", "<<natural stack>>").withExpectedTelemetryProps({ typeofError: "boolean", untrustedOrigin: 1 }),
|
|
608
|
-
}),
|
|
609
|
-
"number": () => ({
|
|
610
|
-
input: 3.14,
|
|
611
|
-
expectedOutput: typicalOutput("3.14", "<<natural stack>>").withExpectedTelemetryProps({ typeofError: "number", untrustedOrigin: 1 }),
|
|
612
|
-
}),
|
|
613
|
-
"symbol": () => ({
|
|
614
|
-
input: Symbol("Unique"),
|
|
615
|
-
expectedOutput: typicalOutput("Symbol(Unique)", "<<natural stack>>").withExpectedTelemetryProps({ typeofError: "symbol", untrustedOrigin: 1 }),
|
|
616
|
-
}),
|
|
617
|
-
"function": () => ({
|
|
618
|
-
input: () => { },
|
|
619
|
-
expectedOutput: typicalOutput("() => { }", "<<natural stack>>").withExpectedTelemetryProps({ typeofError: "function", untrustedOrigin: 1 }),
|
|
620
|
-
}),
|
|
621
|
-
"emptyArray": () => ({
|
|
622
|
-
input: [],
|
|
623
|
-
expectedOutput: typicalOutput("", "<<natural stack>>").withExpectedTelemetryProps({ untrustedOrigin: 1 }),
|
|
624
|
-
}),
|
|
625
|
-
"array": () => ({
|
|
626
|
-
input: [1, 2, 3],
|
|
627
|
-
expectedOutput: typicalOutput("1,2,3", "<<natural stack>>").withExpectedTelemetryProps({ untrustedOrigin: 1 }),
|
|
628
|
-
}),
|
|
629
|
-
};
|
|
630
|
-
function assertMatching(actual, expected, annotations = {}, inputStack) {
|
|
631
|
-
expected.withExpectedTelemetryProps({
|
|
632
|
-
...annotations.props,
|
|
633
|
-
errorInstanceId: actual.errorInstanceId,
|
|
634
|
-
fluidErrorCode: "-", // Present for back-compat
|
|
635
|
-
});
|
|
636
|
-
assertMatchingMessageAndStack(actual, expected, inputStack);
|
|
637
|
-
assert.equal(actual.errorType, expected.errorType, "errorType should match");
|
|
638
|
-
assert.equal(actual.name, expected.name, "name should match");
|
|
639
|
-
assert.equal(actual.errorInstanceId.length, 36, "should be guid-length");
|
|
640
|
-
assert.deepStrictEqual(actual.getTelemetryProperties(), expected.expectedTelemetryProps, "telemetry props should match");
|
|
641
|
-
}
|
|
642
|
-
function assertMatchingMessageAndStack(actual, expected, inputStack) {
|
|
643
|
-
assert.equal(actual.message, expected.message, "message should match");
|
|
644
|
-
const actualStack = actual.stack;
|
|
645
|
-
assert(actualStack !== undefined, "stack should be present as a string");
|
|
646
|
-
if (actualStack.includes("at normalizeError")) {
|
|
647
|
-
// This indicates the stack was populated naturally by new SimpleFluidError
|
|
648
|
-
assert.equal(expected.stack, "<<natural stack>>", "<<natural stack>> hint should be used if not overwritten");
|
|
649
|
-
expected.withExpectedTelemetryProps({ stack: actualStack });
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
assert.equal(actualStack, inputStack, "If stack wasn't generated, it should match input stack");
|
|
653
|
-
assert.equal(expected.stack, "<<stack from input>>", "<<stack from input>> hint should be used if using stack from input error object");
|
|
654
|
-
expected.withExpectedTelemetryProps({ stack: inputStack });
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
for (const annotationCase of Object.keys(annotationCases)) {
|
|
658
|
-
const annotations = annotationCases[annotationCase];
|
|
659
|
-
let doneOnceForThisAnnotationCase = false;
|
|
660
|
-
for (const caseName of Object.keys(testCases)) {
|
|
661
|
-
const getTestCase = testCases[caseName];
|
|
662
|
-
if (!doneOnceForThisAnnotationCase) {
|
|
663
|
-
doneOnceForThisAnnotationCase = true;
|
|
664
|
-
// Each test case only differs by what stack/error are. Test the rest only once per annotation case.
|
|
665
|
-
it(`Normalize untrusted error full validation: (${annotationCase})`, () => {
|
|
666
|
-
// Arrange
|
|
667
|
-
const { input, expectedOutput } = getTestCase();
|
|
668
|
-
// Act
|
|
669
|
-
const normalized = normalizeError(input, annotations);
|
|
670
|
-
// Assert
|
|
671
|
-
assert.notEqual(input, normalized, "input should have yielded a new error object");
|
|
672
|
-
assertMatching(normalized, expectedOutput, annotations, input?.stack);
|
|
673
|
-
if (input instanceof TestFluidError &&
|
|
674
|
-
input.getTelemetryProperties !== undefined) {
|
|
675
|
-
assert(input.gtpSpy.calledOnce, "input.getTelemetryProperties should have been called by normalizeError");
|
|
676
|
-
}
|
|
677
|
-
// Bonus
|
|
678
|
-
normalized.addTelemetryProperties({ foo: "bar" });
|
|
679
|
-
assert.equal(normalized.getTelemetryProperties().foo, "bar", "can add telemetry props after normalization");
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
it(`Normalize untrusted error message/stack: ${caseName} (${annotationCase})`, () => {
|
|
683
|
-
// Arrange
|
|
684
|
-
const { input, expectedOutput } = getTestCase();
|
|
685
|
-
// Act
|
|
686
|
-
const normalized = normalizeError(input, annotations);
|
|
687
|
-
// Assert
|
|
688
|
-
assert.notEqual(input, normalized, "input should have yielded a new error object");
|
|
689
|
-
assertMatchingMessageAndStack(normalized, expectedOutput, input?.stack);
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
});
|
|
695
|
-
/**
|
|
696
|
-
* Create an error missing errorType that will not be recognized as a valid Fluid error
|
|
697
|
-
*/
|
|
698
|
-
const createExternalError = (m) => new Error(m);
|
|
699
|
-
/**
|
|
700
|
-
* Create a simple valid Fluid error
|
|
701
|
-
*/
|
|
702
|
-
const createTestError = (m) => Object.assign(new LoggingError(m), {
|
|
703
|
-
errorType: "someErrorType",
|
|
704
|
-
});
|
|
705
|
-
describe("wrapError", () => {
|
|
706
|
-
it("Copy message, stack, and props", () => {
|
|
707
|
-
const innerError = new LoggingError("hello", { someProp: 123 });
|
|
708
|
-
innerError.stack = "extra special stack";
|
|
709
|
-
const newError = wrapError(innerError, createTestError);
|
|
710
|
-
assert.equal(newError.message, innerError.message, "messages should match");
|
|
711
|
-
assert.equal(newError.stack, innerError.stack, "stacks should match");
|
|
712
|
-
assert.equal(newError.getTelemetryProperties().someProp, 123, "Props should be preserved");
|
|
713
|
-
});
|
|
714
|
-
it("Include matching errorInstanceId and innerErrorInstanceId in telemetry props", () => {
|
|
715
|
-
const innerError = new LoggingError("hello");
|
|
716
|
-
const newError = wrapError(innerError, createTestError);
|
|
717
|
-
assert(newError.errorInstanceId === innerError.errorInstanceId);
|
|
718
|
-
assert(newError.getTelemetryProperties().innerErrorInstanceId === innerError.errorInstanceId);
|
|
719
|
-
});
|
|
720
|
-
it("Properly set untrustedOrigin", () => {
|
|
721
|
-
const untrustedError = createExternalError("untrusted");
|
|
722
|
-
const singleWrapped = wrapError(untrustedError, createTestError);
|
|
723
|
-
assert(singleWrapped.getTelemetryProperties().untrustedOrigin === 1, "wrapped external error should be 'untrustedOrigin'");
|
|
724
|
-
const doubleWrapped = wrapError(singleWrapped, createTestError);
|
|
725
|
-
assert(doubleWrapped.getTelemetryProperties().untrustedOrigin === 1, "doubly-wrapped external error should be 'untrustedOrigin'");
|
|
726
|
-
const normalizedError = normalizeError(untrustedError);
|
|
727
|
-
const wrappedNormalized = wrapError(normalizedError, createTestError);
|
|
728
|
-
assert(wrappedNormalized.getTelemetryProperties().untrustedOrigin === 1, "normalized-then-wrapped external error should be 'untrustedOrigin'");
|
|
729
|
-
const trustedError = createTestError("trusted");
|
|
730
|
-
const wrappedTrusted = wrapError(trustedError, createTestError);
|
|
731
|
-
assert(wrappedTrusted.getTelemetryProperties().untrustedOrigin === undefined, "wrapped Fluid error should not be 'untrustedOrigin'");
|
|
732
|
-
});
|
|
733
|
-
});
|
|
734
|
-
describe("wrapErrorAndLog", () => {
|
|
735
|
-
const mockLogger = new MockLogger();
|
|
736
|
-
const innerError = new LoggingError("hello");
|
|
737
|
-
const newError = wrapErrorAndLog(innerError, createTestError, mockLogger.toTelemetryLogger());
|
|
738
|
-
assert(mockLogger.matchEvents([
|
|
739
|
-
{
|
|
740
|
-
eventName: "WrapError",
|
|
741
|
-
wrappedByErrorInstanceId: newError.errorInstanceId,
|
|
742
|
-
errorInstanceId: newError.errorInstanceId,
|
|
743
|
-
error: "hello",
|
|
744
|
-
},
|
|
745
|
-
]), "Expected the 'WrapError' event to be logged");
|
|
746
|
-
});
|
|
747
|
-
describe("Error Discovery", () => {
|
|
748
|
-
it("isExternalError", () => {
|
|
749
|
-
assert(isExternalError("some string"));
|
|
750
|
-
assert(isExternalError(createExternalError("error message")));
|
|
751
|
-
assert(isExternalError(normalizeError("normalize me but I'm still external")));
|
|
752
|
-
assert(isExternalError(normalizeError(createExternalError("normalize me but I'm still external"))));
|
|
753
|
-
assert(!isExternalError(createTestError("hello")));
|
|
754
|
-
const wrappedError = wrapError("wrap me", createTestError);
|
|
755
|
-
assert(!isExternalError(wrappedError));
|
|
756
|
-
assert(wrappedError.getTelemetryProperties().untrustedOrigin === 1); // But it should still say untrustedOrigin
|
|
757
|
-
assert(!isExternalError(new LoggingError("testLoggingError")));
|
|
758
|
-
});
|
|
759
|
-
it("isValidLegacyError", () => {
|
|
760
|
-
const validLegacyError = {
|
|
761
|
-
message: "testMessage",
|
|
762
|
-
errorType: "someErrorType",
|
|
763
|
-
getTelemetryProperties: () => { },
|
|
764
|
-
addTelemetryProperties: () => { },
|
|
765
|
-
};
|
|
766
|
-
assert.strictEqual(isValidLegacyError(validLegacyError), true);
|
|
767
|
-
assert.strictEqual(isValidLegacyError({ ...validLegacyError, message: undefined }), false);
|
|
768
|
-
assert.strictEqual(isValidLegacyError({ ...validLegacyError, errorType: undefined }), false);
|
|
769
|
-
assert.strictEqual(isValidLegacyError({ ...validLegacyError, getTelemetryProperties: undefined }), false);
|
|
770
|
-
assert.strictEqual(isValidLegacyError({ ...validLegacyError, addTelemetryProperties: undefined }), false);
|
|
771
|
-
});
|
|
772
|
-
// I copied the old version of isFluidError here, it depends on fluidErrorCode.
|
|
773
|
-
// I want to make sure that an error built on LoggingError that otherwise matches isFluidError
|
|
774
|
-
// will match isFluidError in old code (e.g. when an error flows across layers)
|
|
775
|
-
function isFluidError_old(e) {
|
|
776
|
-
const hasTelemetryPropFunctions = (x) => typeof x?.getTelemetryProperties === "function" &&
|
|
777
|
-
typeof x?.addTelemetryProperties === "function";
|
|
778
|
-
return (typeof e?.errorType === "string" &&
|
|
779
|
-
typeof e?.fluidErrorCode === "string" &&
|
|
780
|
-
typeof e?.message === "string" &&
|
|
781
|
-
hasErrorInstanceId(e) &&
|
|
782
|
-
hasTelemetryPropFunctions(e));
|
|
783
|
-
}
|
|
784
|
-
function testFluidError(isFluidErrorImpl, isOld) {
|
|
785
|
-
it(`isFluidError${isOld ? "_old" : ""}`, () => {
|
|
786
|
-
assert(!isFluidErrorImpl(new Error("hello")), "Plain Error object is not a Fluid Error");
|
|
787
|
-
assert(!isFluidErrorImpl(new LoggingError("hello")), "LoggingError is not a Fluid Error (no errorType)");
|
|
788
|
-
assert(!isFluidErrorImpl(Object.assign(new Error("hello"), {
|
|
789
|
-
errorType: "someErrorType",
|
|
790
|
-
_errorInstanceId: "12345",
|
|
791
|
-
})), "Error with errorType and errorInstanceId but without telemetry prop fns is not a Fluid Error");
|
|
792
|
-
assert(!isFluidErrorImpl(createExternalError("hello")), "Error without errorType is not a Fluid Error");
|
|
793
|
-
assert(!isFluidErrorImpl(Object.assign(createTestError("hello"), { _errorInstanceId: undefined })), "Valid Fluid Error with errorInstanceId removed is not a Fluid Error");
|
|
794
|
-
assert(isFluidErrorImpl(createTestError("hello")), "Valid Fluid Error is a Fluid Error");
|
|
795
|
-
assert.equal(!isOld, isFluidErrorImpl(Object.assign(createTestError("hello"), { fluidErrorCode: undefined })), "Old isFluidError impl should require fluidErrorCode but New should not");
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
testFluidError(isFluidError, false /* isOld */);
|
|
799
|
-
testFluidError(isFluidError_old, true /* isOld */);
|
|
800
|
-
});
|
|
801
|
-
//# sourceMappingURL=errorLogging.spec.js.map
|