@fluidframework/telemetry-utils 2.0.0-dev.2.2.0.111723 → 2.0.0-dev.3.1.0.125672
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/.eslintrc.js +11 -13
- package/.mocharc.js +2 -2
- package/api-extractor.json +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/debugLogger.d.ts.map +1 -1
- package/dist/debugLogger.js.map +1 -1
- package/dist/errorLogging.d.ts +8 -7
- package/dist/errorLogging.d.ts.map +1 -1
- package/dist/errorLogging.js +23 -20
- package/dist/errorLogging.js.map +1 -1
- package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/dist/eventEmitterWithErrorHandling.js.map +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 +4 -4
- package/dist/fluidErrorBase.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +15 -4
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +69 -11
- package/dist/logger.js.map +1 -1
- package/dist/mockLogger.d.ts +12 -2
- package/dist/mockLogger.d.ts.map +1 -1
- package/dist/mockLogger.js +24 -2
- package/dist/mockLogger.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/sampledTelemetryHelper.d.ts.map +1 -1
- package/dist/sampledTelemetryHelper.js.map +1 -1
- package/dist/telemetryTypes.d.ts +81 -0
- package/dist/telemetryTypes.d.ts.map +1 -0
- package/dist/telemetryTypes.js +7 -0
- package/dist/telemetryTypes.js.map +1 -0
- package/dist/thresholdCounter.d.ts.map +1 -1
- package/dist/thresholdCounter.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +3 -3
- package/lib/config.js.map +1 -1
- package/lib/debugLogger.d.ts.map +1 -1
- package/lib/debugLogger.js +1 -1
- package/lib/debugLogger.js.map +1 -1
- package/lib/errorLogging.d.ts +8 -7
- package/lib/errorLogging.d.ts.map +1 -1
- package/lib/errorLogging.js +23 -20
- package/lib/errorLogging.js.map +1 -1
- package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/lib/eventEmitterWithErrorHandling.js.map +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 +4 -4
- package/lib/fluidErrorBase.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/logger.d.ts +15 -4
- package/lib/logger.d.ts.map +1 -1
- package/lib/logger.js +69 -12
- package/lib/logger.js.map +1 -1
- package/lib/mockLogger.d.ts +12 -2
- package/lib/mockLogger.d.ts.map +1 -1
- package/lib/mockLogger.js +24 -2
- package/lib/mockLogger.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/sampledTelemetryHelper.d.ts.map +1 -1
- package/lib/sampledTelemetryHelper.js.map +1 -1
- package/lib/telemetryTypes.d.ts +81 -0
- package/lib/telemetryTypes.d.ts.map +1 -0
- package/lib/telemetryTypes.js +6 -0
- package/lib/telemetryTypes.js.map +1 -0
- package/lib/thresholdCounter.d.ts.map +1 -1
- package/lib/thresholdCounter.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js.map +1 -1
- package/package.json +14 -12
- package/prettier.config.cjs +1 -1
- package/src/config.ts +173 -172
- package/src/debugLogger.ts +118 -111
- package/src/errorLogging.ts +306 -300
- package/src/eventEmitterWithErrorHandling.ts +16 -12
- package/src/events.ts +26 -26
- package/src/fluidErrorBase.ts +42 -38
- package/src/index.ts +29 -9
- package/src/logger.ts +568 -500
- package/src/mockLogger.ts +118 -88
- package/src/packageVersion.ts +1 -1
- package/src/sampledTelemetryHelper.ts +122 -122
- package/src/telemetryTypes.ts +96 -0
- package/src/thresholdCounter.ts +34 -34
- package/src/utils.ts +15 -15
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +9 -13
package/src/errorLogging.ts
CHANGED
|
@@ -4,89 +4,89 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
ILoggingError,
|
|
8
|
+
ITaggedTelemetryPropertyType,
|
|
9
|
+
ITelemetryLogger,
|
|
10
|
+
ITelemetryProperties,
|
|
11
|
+
TelemetryEventPropertyType,
|
|
12
12
|
} from "@fluidframework/common-definitions";
|
|
13
13
|
import { v4 as uuid } from "uuid";
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
hasErrorInstanceId,
|
|
16
|
+
IFluidErrorBase,
|
|
17
|
+
isFluidError,
|
|
18
|
+
isValidLegacyError,
|
|
19
19
|
} from "./fluidErrorBase";
|
|
20
|
+
import { ITaggedTelemetryPropertyTypeExt, TelemetryEventPropertyTypeExt } from "./telemetryTypes";
|
|
20
21
|
|
|
21
22
|
/** @returns true if value is an object but neither null nor an array */
|
|
22
23
|
const isRegularObject = (value: any): boolean => {
|
|
23
|
-
|
|
24
|
+
return value !== null && !Array.isArray(value) && typeof value === "object";
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
/** Inspect the given error for common "safe" props and return them */
|
|
27
28
|
export function extractLogSafeErrorProperties(error: any, sanitizeStack: boolean) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return safeProps;
|
|
29
|
+
const removeMessageFromStack = (stack: string, errorName?: string) => {
|
|
30
|
+
if (!sanitizeStack) {
|
|
31
|
+
return stack;
|
|
32
|
+
}
|
|
33
|
+
const stackFrames = stack.split("\n");
|
|
34
|
+
stackFrames.shift(); // Remove "[ErrorName]: [ErrorMessage]"
|
|
35
|
+
if (errorName !== undefined) {
|
|
36
|
+
stackFrames.unshift(errorName); // Add "[ErrorName]"
|
|
37
|
+
}
|
|
38
|
+
return stackFrames.join("\n");
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const message = typeof error?.message === "string" ? (error.message as string) : String(error);
|
|
42
|
+
|
|
43
|
+
const safeProps: { message: string; errorType?: string; stack?: string } = {
|
|
44
|
+
message,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (isRegularObject(error)) {
|
|
48
|
+
const { errorType, stack, name } = error;
|
|
49
|
+
|
|
50
|
+
if (typeof errorType === "string") {
|
|
51
|
+
safeProps.errorType = errorType;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof stack === "string") {
|
|
55
|
+
const errorName = typeof name === "string" ? name : undefined;
|
|
56
|
+
safeProps.stack = removeMessageFromStack(stack, errorName);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return safeProps;
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
/** type guard for ILoggingError interface */
|
|
65
|
-
export const isILoggingError = (x: any): x is ILoggingError =>
|
|
64
|
+
export const isILoggingError = (x: any): x is ILoggingError =>
|
|
65
|
+
typeof x?.getTelemetryProperties === "function";
|
|
66
66
|
|
|
67
67
|
/** Copy props from source onto target, but do not overwrite an existing prop that matches */
|
|
68
68
|
function copyProps(target: ITelemetryProperties | LoggingError, source: ITelemetryProperties) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
for (const key of Object.keys(source)) {
|
|
70
|
+
if (target[key] === undefined) {
|
|
71
|
+
target[key] = source[key];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/** Metadata to annotate an error object when annotating or normalizing it */
|
|
77
77
|
export interface IFluidErrorAnnotations {
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
/** Telemetry props to log with the error */
|
|
79
|
+
props?: ITelemetryProperties;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/** For backwards compatibility with pre-errorInstanceId valid errors */
|
|
83
83
|
function patchLegacyError(
|
|
84
|
-
|
|
84
|
+
legacyError: Omit<IFluidErrorBase, "errorInstanceId">,
|
|
85
85
|
): asserts legacyError is IFluidErrorBase {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
const patchMe: { -readonly [P in "errorInstanceId"]?: IFluidErrorBase[P] } = legacyError as any;
|
|
87
|
+
if (patchMe.errorInstanceId === undefined) {
|
|
88
|
+
patchMe.errorInstanceId = uuid();
|
|
89
|
+
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/**
|
|
@@ -96,50 +96,50 @@ function patchLegacyError(
|
|
|
96
96
|
* @param annotations - Annotations to apply to the normalized error
|
|
97
97
|
*/
|
|
98
98
|
export function normalizeError(
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
error: unknown,
|
|
100
|
+
annotations: IFluidErrorAnnotations = {},
|
|
101
101
|
): IFluidErrorBase {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
102
|
+
// Back-compat, while IFluidErrorBase is rolled out
|
|
103
|
+
if (isValidLegacyError(error)) {
|
|
104
|
+
patchLegacyError(error);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (isFluidError(error)) {
|
|
108
|
+
// We can simply add the telemetry props to the error and return it
|
|
109
|
+
error.addTelemetryProperties(annotations.props ?? {});
|
|
110
|
+
return error;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// We have to construct a new Fluid Error, copying safe properties over
|
|
114
|
+
const { message, stack } = extractLogSafeErrorProperties(error, false /* sanitizeStack */);
|
|
115
|
+
const fluidError: IFluidErrorBase = new NormalizedLoggingError({
|
|
116
|
+
message,
|
|
117
|
+
stack,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// We need to preserve these properties which are used in a non-typesafe way throughout driver code (see #8743)
|
|
121
|
+
// Anywhere they are set should be on a valid Fluid Error that would have been returned above,
|
|
122
|
+
// but we can't prove it with the types, so adding this defensive measure.
|
|
123
|
+
if (typeof error === "object" && error !== null) {
|
|
124
|
+
const { canRetry, retryAfterSeconds } = error as any;
|
|
125
|
+
Object.assign(normalizeError, { canRetry, retryAfterSeconds });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (typeof error !== "object") {
|
|
129
|
+
// This is only interesting for non-objects
|
|
130
|
+
fluidError.addTelemetryProperties({ typeofError: typeof error });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const errorTelemetryProps = LoggingError.typeCheck(error)
|
|
134
|
+
? error.getTelemetryProperties()
|
|
135
|
+
: { untrustedOrigin: 1 }; // This will let us filter errors that did not originate from our own codebase
|
|
136
|
+
|
|
137
|
+
fluidError.addTelemetryProperties({
|
|
138
|
+
...errorTelemetryProps,
|
|
139
|
+
...annotations.props,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return fluidError;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
let stackPopulatedOnCreation: boolean | undefined;
|
|
@@ -154,25 +154,25 @@ let stackPopulatedOnCreation: boolean | undefined;
|
|
|
154
154
|
* @returns Error object that has stack populated.
|
|
155
155
|
*/
|
|
156
156
|
export function generateErrorWithStack(): Error {
|
|
157
|
-
|
|
157
|
+
const err = new Error("<<generated stack>>");
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
if (stackPopulatedOnCreation === undefined) {
|
|
160
|
+
stackPopulatedOnCreation = err.stack !== undefined;
|
|
161
|
+
}
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
if (stackPopulatedOnCreation) {
|
|
164
|
+
return err;
|
|
165
|
+
}
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
try {
|
|
168
|
+
throw err;
|
|
169
|
+
} catch (e) {
|
|
170
|
+
return e as Error;
|
|
171
|
+
}
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
export function generateStack(): string | undefined {
|
|
175
|
-
|
|
175
|
+
return generateErrorWithStack().stack;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/**
|
|
@@ -183,72 +183,72 @@ export function generateStack(): string | undefined {
|
|
|
183
183
|
* @returns A new error object "wrapping" the given error
|
|
184
184
|
*/
|
|
185
185
|
export function wrapError<T extends LoggingError>(
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
innerError: unknown,
|
|
187
|
+
newErrorFn: (message: string) => T,
|
|
188
188
|
): T {
|
|
189
|
-
|
|
190
|
-
message,
|
|
191
|
-
stack,
|
|
192
|
-
} = extractLogSafeErrorProperties(innerError, false /* sanitizeStack */);
|
|
189
|
+
const { message, stack } = extractLogSafeErrorProperties(innerError, false /* sanitizeStack */);
|
|
193
190
|
|
|
194
|
-
|
|
191
|
+
const newError = newErrorFn(message);
|
|
195
192
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
if (stack !== undefined) {
|
|
194
|
+
overwriteStack(newError, stack);
|
|
195
|
+
}
|
|
199
196
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
197
|
+
// Mark external errors with untrustedOrigin flag
|
|
198
|
+
if (isExternalError(innerError)) {
|
|
199
|
+
newError.addTelemetryProperties({ untrustedOrigin: 1 });
|
|
200
|
+
}
|
|
204
201
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
202
|
+
// Reuse errorInstanceId
|
|
203
|
+
if (hasErrorInstanceId(innerError)) {
|
|
204
|
+
newError.overwriteErrorInstanceId(innerError.errorInstanceId);
|
|
208
205
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
206
|
+
// For "back-compat" in the logs
|
|
207
|
+
newError.addTelemetryProperties({ innerErrorInstanceId: innerError.errorInstanceId });
|
|
208
|
+
}
|
|
212
209
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
// Lastly, copy over all other telemetry properties. Note these will not overwrite existing properties
|
|
211
|
+
// This will include the untrustedOrigin property if the inner error itself was created from an external error
|
|
212
|
+
if (isILoggingError(innerError)) {
|
|
213
|
+
newError.addTelemetryProperties(innerError.getTelemetryProperties());
|
|
214
|
+
}
|
|
218
215
|
|
|
219
|
-
|
|
216
|
+
return newError;
|
|
220
217
|
}
|
|
221
218
|
|
|
222
219
|
/** The same as wrapError, but also logs the innerError, including the wrapping error's instance id */
|
|
223
220
|
export function wrapErrorAndLog<T extends LoggingError>(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
221
|
+
innerError: unknown,
|
|
222
|
+
newErrorFn: (message: string) => T,
|
|
223
|
+
logger: ITelemetryLogger,
|
|
227
224
|
) {
|
|
228
|
-
|
|
225
|
+
const newError = wrapError(innerError, newErrorFn);
|
|
229
226
|
|
|
230
|
-
|
|
231
|
-
|
|
227
|
+
// This will match innerError.errorInstanceId if present (see wrapError)
|
|
228
|
+
const errorInstanceId = newError.errorInstanceId;
|
|
232
229
|
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
// For "back-compat" in the logs
|
|
231
|
+
const wrappedByErrorInstanceId = errorInstanceId;
|
|
235
232
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
233
|
+
logger.sendTelemetryEvent(
|
|
234
|
+
{
|
|
235
|
+
eventName: "WrapError",
|
|
236
|
+
errorInstanceId,
|
|
237
|
+
wrappedByErrorInstanceId,
|
|
238
|
+
},
|
|
239
|
+
innerError,
|
|
240
|
+
);
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
return newError;
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
function overwriteStack(error: IFluidErrorBase | LoggingError, stack: string) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
246
|
+
// supposedly setting stack on an Error can throw.
|
|
247
|
+
try {
|
|
248
|
+
Object.assign(error, { stack });
|
|
249
|
+
} catch (errorSettingStack) {
|
|
250
|
+
error.addTelemetryProperties({ stack2: stack });
|
|
251
|
+
}
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
/**
|
|
@@ -257,23 +257,25 @@ function overwriteStack(error: IFluidErrorBase | LoggingError, stack: string) {
|
|
|
257
257
|
* or wrapped in a well-known error type
|
|
258
258
|
*/
|
|
259
259
|
export function isExternalError(e: any): boolean {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
260
|
+
// LoggingErrors are an internal FF error type. However, an external error can be converted
|
|
261
|
+
// into a LoggingError if it is normalized. In this case we must use the untrustedOrigin flag to
|
|
262
|
+
// determine whether the original error was infact external.
|
|
263
|
+
if (LoggingError.typeCheck(e)) {
|
|
264
|
+
if ((e as NormalizedLoggingError).errorType === NORMALIZED_ERROR_TYPE) {
|
|
265
|
+
return e.getTelemetryProperties().untrustedOrigin === 1;
|
|
266
|
+
}
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
return !isValidLegacyError(e);
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
/**
|
|
273
|
-
* Type guard to identify if a particular
|
|
273
|
+
* Type guard to identify if a particular telemetry property appears to be a tagged telemetry property
|
|
274
274
|
*/
|
|
275
|
-
export function isTaggedTelemetryPropertyValue(
|
|
276
|
-
|
|
275
|
+
export function isTaggedTelemetryPropertyValue(
|
|
276
|
+
x: ITaggedTelemetryPropertyTypeExt | TelemetryEventPropertyTypeExt,
|
|
277
|
+
): x is ITaggedTelemetryPropertyType | ITaggedTelemetryPropertyTypeExt {
|
|
278
|
+
return typeof (x as any)?.tag === "string";
|
|
277
279
|
}
|
|
278
280
|
|
|
279
281
|
/**
|
|
@@ -283,51 +285,51 @@ export function isTaggedTelemetryPropertyValue(x: any): x is ITaggedTelemetryPro
|
|
|
283
285
|
* otherwise returns null since this is what we support at the moment.
|
|
284
286
|
*/
|
|
285
287
|
function filterValidTelemetryProps(x: any, key: string): TelemetryEventPropertyType {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
288
|
+
if (Array.isArray(x) && x.every((val) => isTelemetryEventPropertyValue(val))) {
|
|
289
|
+
return JSON.stringify(x);
|
|
290
|
+
}
|
|
291
|
+
if (isTelemetryEventPropertyValue(x)) {
|
|
292
|
+
return x;
|
|
293
|
+
}
|
|
294
|
+
// We don't support logging arbitrary objects
|
|
295
|
+
console.error(`UnSupported Format of Logging Error Property for key ${key}:`, x);
|
|
296
|
+
return "REDACTED (arbitrary object)";
|
|
295
297
|
}
|
|
296
298
|
|
|
297
299
|
// checking type of x, returns false if x is null
|
|
298
300
|
function isTelemetryEventPropertyValue(x: any): x is TelemetryEventPropertyType {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
301
|
+
switch (typeof x) {
|
|
302
|
+
case "string":
|
|
303
|
+
case "number":
|
|
304
|
+
case "boolean":
|
|
305
|
+
case "undefined":
|
|
306
|
+
return true;
|
|
307
|
+
default:
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
308
310
|
}
|
|
309
311
|
/**
|
|
310
312
|
* Walk an object's enumerable properties to find those fit for telemetry.
|
|
311
313
|
*/
|
|
312
314
|
function getValidTelemetryProps(obj: any, keysToOmit: Set<string>): ITelemetryProperties {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
315
|
+
const props: ITelemetryProperties = {};
|
|
316
|
+
for (const key of Object.keys(obj)) {
|
|
317
|
+
if (keysToOmit.has(key)) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const val = obj[key];
|
|
321
|
+
|
|
322
|
+
// ensure only valid props get logged, since props of logging error could be in any shape
|
|
323
|
+
if (isTaggedTelemetryPropertyValue(val)) {
|
|
324
|
+
props[key] = {
|
|
325
|
+
value: filterValidTelemetryProps(val.value, key),
|
|
326
|
+
tag: val.tag,
|
|
327
|
+
};
|
|
328
|
+
} else {
|
|
329
|
+
props[key] = filterValidTelemetryProps(val, key);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return props;
|
|
331
333
|
}
|
|
332
334
|
|
|
333
335
|
/**
|
|
@@ -336,18 +338,18 @@ function getValidTelemetryProps(obj: any, keysToOmit: Set<string>): ITelemetryPr
|
|
|
336
338
|
* Avoids runtime errors with circular references.
|
|
337
339
|
* Not ideal, as will cut values that are not necessarily circular references.
|
|
338
340
|
* Could be improved by implementing Node's util.inspect() for browser (minus all the coloring code)
|
|
339
|
-
*/
|
|
341
|
+
*/
|
|
340
342
|
export const getCircularReplacer = () => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
343
|
+
const seen = new WeakSet();
|
|
344
|
+
return (key: string, value: any): any => {
|
|
345
|
+
if (typeof value === "object" && value !== null) {
|
|
346
|
+
if (seen.has(value)) {
|
|
347
|
+
return "<removed/circular>";
|
|
348
|
+
}
|
|
349
|
+
seen.add(value);
|
|
350
|
+
}
|
|
351
|
+
return value;
|
|
352
|
+
};
|
|
351
353
|
};
|
|
352
354
|
|
|
353
355
|
/**
|
|
@@ -357,90 +359,94 @@ export const getCircularReplacer = () => {
|
|
|
357
359
|
*
|
|
358
360
|
* PLEASE take care to avoid setting sensitive data on this object without proper tagging!
|
|
359
361
|
*/
|
|
360
|
-
export class LoggingError
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
362
|
+
export class LoggingError
|
|
363
|
+
extends Error
|
|
364
|
+
implements ILoggingError, Omit<IFluidErrorBase, "errorType">
|
|
365
|
+
{
|
|
366
|
+
private _errorInstanceId = uuid();
|
|
367
|
+
get errorInstanceId() {
|
|
368
|
+
return this._errorInstanceId;
|
|
369
|
+
}
|
|
370
|
+
overwriteErrorInstanceId(id: string) {
|
|
371
|
+
this._errorInstanceId = id;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Back-compat to appease isFluidError typeguard in old code that may handle this error */
|
|
375
|
+
// @ts-expect-error - This field shouldn't be referenced in the current version, but needs to exist at runtime.
|
|
376
|
+
private readonly fluidErrorCode: "-" = "-";
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Create a new LoggingError
|
|
380
|
+
* @param message - Error message to use for Error base class
|
|
381
|
+
* @param props - telemetry props to include on the error for when it's logged
|
|
382
|
+
* @param omitPropsFromLogging - properties by name to omit from telemetry props
|
|
383
|
+
*/
|
|
384
|
+
constructor(
|
|
385
|
+
message: string,
|
|
386
|
+
props?: ITelemetryProperties,
|
|
387
|
+
private readonly omitPropsFromLogging: Set<string> = new Set(),
|
|
388
|
+
) {
|
|
389
|
+
super(message);
|
|
390
|
+
|
|
391
|
+
// Don't log this list itself, or the private _errorInstanceId
|
|
392
|
+
omitPropsFromLogging.add("omitPropsFromLogging");
|
|
393
|
+
omitPropsFromLogging.add("_errorInstanceId");
|
|
394
|
+
|
|
395
|
+
if (props) {
|
|
396
|
+
this.addTelemetryProperties(props);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Determines if a given object is an instance of a LoggingError
|
|
402
|
+
* @param object - any object
|
|
403
|
+
* @returns - true if the object is an instance of a LoggingError, false if not.
|
|
404
|
+
*/
|
|
405
|
+
public static typeCheck(object: unknown): object is LoggingError {
|
|
406
|
+
if (typeof object === "object" && object !== null) {
|
|
407
|
+
return (
|
|
408
|
+
typeof (object as LoggingError).addTelemetryProperties === "function" &&
|
|
409
|
+
typeof (object as LoggingError).getTelemetryProperties === "function" &&
|
|
410
|
+
typeof (object as LoggingError).errorInstanceId === "string"
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Add additional properties to be logged
|
|
418
|
+
*/
|
|
419
|
+
public addTelemetryProperties(props: ITelemetryProperties) {
|
|
420
|
+
copyProps(this, props);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get all properties fit to be logged to telemetry for this error
|
|
425
|
+
*/
|
|
426
|
+
public getTelemetryProperties(): ITelemetryProperties {
|
|
427
|
+
const taggableProps = getValidTelemetryProps(this, this.omitPropsFromLogging);
|
|
428
|
+
// Include non-enumerable props that are not returned by getValidTelemetryProps
|
|
429
|
+
return {
|
|
430
|
+
...taggableProps,
|
|
431
|
+
stack: this.stack,
|
|
432
|
+
message: this.message,
|
|
433
|
+
errorInstanceId: this._errorInstanceId,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
425
436
|
}
|
|
426
437
|
|
|
427
438
|
/** The Error class used when normalizing an external error */
|
|
428
439
|
export const NORMALIZED_ERROR_TYPE = "genericError";
|
|
429
440
|
class NormalizedLoggingError extends LoggingError {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (errorProps.stack !== undefined) {
|
|
443
|
-
overwriteStack(this, errorProps.stack);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
441
|
+
// errorType "genericError" is used as a default value throughout the code.
|
|
442
|
+
// Note that this matches ContainerErrorType/DriverErrorType's genericError
|
|
443
|
+
errorType = NORMALIZED_ERROR_TYPE;
|
|
444
|
+
|
|
445
|
+
constructor(errorProps: Pick<IFluidErrorBase, "message" | "stack">) {
|
|
446
|
+
super(errorProps.message);
|
|
447
|
+
|
|
448
|
+
if (errorProps.stack !== undefined) {
|
|
449
|
+
overwriteStack(this, errorProps.stack);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
446
452
|
}
|