@fluidframework/telemetry-utils 0.53.0 → 0.54.2
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/dist/config.d.ts +65 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +212 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +5 -0
- package/dist/logger.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/lib/config.d.ts +65 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +204 -0
- package/lib/config.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/logger.d.ts.map +1 -1
- package/lib/logger.js +5 -0
- package/lib/logger.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/package.json +3 -3
- package/src/config.ts +265 -0
- package/src/index.ts +9 -0
- package/src/logger.ts +15 -2
- package/src/packageVersion.ts +1 -1
package/src/config.ts
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
6
|
+
import { Lazy } from "@fluidframework/common-utils";
|
|
7
|
+
|
|
8
|
+
export type ConfigTypes = string | number | boolean | number[] | string[] | boolean[] | undefined;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Base interface for providing configurations to enable/disable/control features
|
|
12
|
+
*/
|
|
13
|
+
export interface IConfigProviderBase {
|
|
14
|
+
getRawConfig(name: string): ConfigTypes;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Explicitly typed interface for reading configurations
|
|
19
|
+
*/
|
|
20
|
+
export interface IConfigProvider extends IConfigProviderBase {
|
|
21
|
+
getBoolean(name: string): boolean | undefined;
|
|
22
|
+
getNumber(name: string): number | undefined;
|
|
23
|
+
getString(name: string): string | undefined;
|
|
24
|
+
getBooleanArray(name: string): boolean[] | undefined;
|
|
25
|
+
getNumberArray(name: string): number[] | undefined;
|
|
26
|
+
getStringArray(name: string): string[] | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a base configuration provider based on `sessionStorage`
|
|
30
|
+
*
|
|
31
|
+
* @returns A lazy initialized base configuration provider with `sessionStorage` as the underlying config store
|
|
32
|
+
*/
|
|
33
|
+
export const sessionStorageConfigProvider =
|
|
34
|
+
new Lazy<IConfigProviderBase>(() => inMemoryConfigProvider(safeSessionStorage()));
|
|
35
|
+
|
|
36
|
+
const NullConfigProvider: IConfigProviderBase = {
|
|
37
|
+
getRawConfig: () => undefined,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a base configuration provider based on the supplied `Storage` instance
|
|
42
|
+
*
|
|
43
|
+
* @param storage - instance of `Storage` to be used as storage media for the config
|
|
44
|
+
* @returns A base configuration provider with
|
|
45
|
+
* the supplied `Storage` instance as the underlying config store
|
|
46
|
+
*/
|
|
47
|
+
export const inMemoryConfigProvider =
|
|
48
|
+
(storage: Storage | undefined): IConfigProviderBase => {
|
|
49
|
+
if (storage !== undefined && storage !== null) {
|
|
50
|
+
return new CachedConfigProvider({
|
|
51
|
+
getRawConfig: (name: string) => {
|
|
52
|
+
try {
|
|
53
|
+
return stronglyTypedParse(storage.getItem(name) ?? undefined)?.raw;
|
|
54
|
+
} catch { }
|
|
55
|
+
return undefined;
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return NullConfigProvider;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
interface ConfigTypeStringToType {
|
|
63
|
+
number: number;
|
|
64
|
+
string: string;
|
|
65
|
+
boolean: boolean;
|
|
66
|
+
["number[]"]: number[];
|
|
67
|
+
["string[]"]: string[];
|
|
68
|
+
["boolean[]"]: boolean[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type PrimitiveTypeStrings = "number" | "string" | "boolean";
|
|
72
|
+
|
|
73
|
+
function isPrimitiveType(type: string): type is PrimitiveTypeStrings {
|
|
74
|
+
switch (type) {
|
|
75
|
+
case "boolean":
|
|
76
|
+
case "number":
|
|
77
|
+
case "string":
|
|
78
|
+
return true;
|
|
79
|
+
default:
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface StronglyTypedValue extends Partial<ConfigTypeStringToType> {
|
|
85
|
+
raw: ConfigTypes;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Takes any supported config type, and returns the value with a strong type. If the type of
|
|
89
|
+
* the config is not a supported type undefined will be returned.
|
|
90
|
+
* The user of this function should cache the result to avoid duplicated work.
|
|
91
|
+
*
|
|
92
|
+
* Strings will be attempted to be parsed and coerced into a strong config type.
|
|
93
|
+
* if it is not possible to parsed and coerce a string to a strong config type the original string
|
|
94
|
+
* will be return with a string type for the consumer to handle further if necessary.
|
|
95
|
+
*/
|
|
96
|
+
function stronglyTypedParse(input: ConfigTypes): StronglyTypedValue | undefined {
|
|
97
|
+
let output: ConfigTypes = input;
|
|
98
|
+
let defaultReturn: Pick<StronglyTypedValue,"raw" | "string"> | undefined;
|
|
99
|
+
// we do special handling for strings to try and coerce
|
|
100
|
+
// them into a config type if we can. This makes it easy
|
|
101
|
+
// for config sources like sessionStorage which only
|
|
102
|
+
// holds strings
|
|
103
|
+
if (typeof input === "string") {
|
|
104
|
+
try {
|
|
105
|
+
output = JSON.parse(input);
|
|
106
|
+
// we succeeded in parsing, but we don't support parsing
|
|
107
|
+
// for any object as we can't do it type safely
|
|
108
|
+
// so in this case, the default return will be string
|
|
109
|
+
// rather than undefined, and the consumer
|
|
110
|
+
// can parse, as we don't want to provide
|
|
111
|
+
// a false sense of security by just
|
|
112
|
+
// casting.
|
|
113
|
+
defaultReturn = { raw: input, string: input };
|
|
114
|
+
} catch { }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (output === undefined) {
|
|
118
|
+
return defaultReturn;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const outputType = typeof output;
|
|
122
|
+
if (isPrimitiveType(outputType)) {
|
|
123
|
+
return { ...defaultReturn, raw: input, [outputType]: output };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (Array.isArray(output)) {
|
|
127
|
+
const firstType = typeof output[0];
|
|
128
|
+
// ensure the first elements is a primitive type
|
|
129
|
+
if (!isPrimitiveType(firstType)) {
|
|
130
|
+
return defaultReturn;
|
|
131
|
+
}
|
|
132
|
+
// ensue all the elements types are homogeneous
|
|
133
|
+
// aka they all have the same type as the first
|
|
134
|
+
for (const v of output) {
|
|
135
|
+
if (typeof v !== firstType) {
|
|
136
|
+
return defaultReturn;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { ...defaultReturn, raw: input, [`${firstType}[]`]: output };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return defaultReturn;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Referencing the `sessionStorage` variable can throw in some environments such as Node */
|
|
146
|
+
const safeSessionStorage = (): Storage | undefined => {
|
|
147
|
+
try {
|
|
148
|
+
return sessionStorage !== null ? sessionStorage : undefined;
|
|
149
|
+
} catch { return undefined; }
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Implementation of {@link IConfigProvider} which contains nested {@link IConfigProviderBase} instances
|
|
154
|
+
*/
|
|
155
|
+
export class CachedConfigProvider implements IConfigProvider {
|
|
156
|
+
private readonly configCache = new Map<string, StronglyTypedValue>();
|
|
157
|
+
private readonly orderedBaseProviders: (IConfigProviderBase | undefined)[];
|
|
158
|
+
|
|
159
|
+
constructor(
|
|
160
|
+
... orderedBaseProviders: (IConfigProviderBase | undefined)[]
|
|
161
|
+
) {
|
|
162
|
+
this.orderedBaseProviders = [];
|
|
163
|
+
const knownProviders = new Set<IConfigProviderBase>();
|
|
164
|
+
const candidateProviders = [...orderedBaseProviders];
|
|
165
|
+
while (candidateProviders.length > 0) {
|
|
166
|
+
const baseProvider = candidateProviders.shift()!;
|
|
167
|
+
if (baseProvider !== undefined
|
|
168
|
+
&& isConfigProviderBase(baseProvider)
|
|
169
|
+
&& !knownProviders.has(baseProvider)
|
|
170
|
+
) {
|
|
171
|
+
knownProviders.add(baseProvider);
|
|
172
|
+
if (baseProvider instanceof CachedConfigProvider) {
|
|
173
|
+
candidateProviders.push(...baseProvider.orderedBaseProviders);
|
|
174
|
+
} else {
|
|
175
|
+
this.orderedBaseProviders.push(baseProvider);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
getBoolean(name: string): boolean | undefined {
|
|
181
|
+
return this.getCacheEntry(name)?.boolean;
|
|
182
|
+
}
|
|
183
|
+
getNumber(name: string): number | undefined {
|
|
184
|
+
return this.getCacheEntry(name)?.number;
|
|
185
|
+
}
|
|
186
|
+
getString(name: string): string | undefined {
|
|
187
|
+
return this.getCacheEntry(name)?.string;
|
|
188
|
+
}
|
|
189
|
+
getBooleanArray(name: string): boolean[] | undefined {
|
|
190
|
+
return this.getCacheEntry(name)?.["boolean[]"];
|
|
191
|
+
}
|
|
192
|
+
getNumberArray(name: string): number[] | undefined {
|
|
193
|
+
return this.getCacheEntry(name)?.["number[]"];
|
|
194
|
+
}
|
|
195
|
+
getStringArray(name: string): string[] | undefined {
|
|
196
|
+
return this.getCacheEntry(name)?.["string[]"];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
getRawConfig(name: string): ConfigTypes {
|
|
200
|
+
return this.getCacheEntry(name)?.raw;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private getCacheEntry(name: string): StronglyTypedValue | undefined {
|
|
204
|
+
if (!this.configCache.has(name)) {
|
|
205
|
+
for (const provider of this.orderedBaseProviders) {
|
|
206
|
+
const parsed = stronglyTypedParse(provider?.getRawConfig(name));
|
|
207
|
+
if (parsed !== undefined) {
|
|
208
|
+
this.configCache.set(name, parsed);
|
|
209
|
+
return parsed;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// configs are immutable, if the first lookup returned no results, all lookups should
|
|
213
|
+
this.configCache.set(name, { raw: undefined });
|
|
214
|
+
}
|
|
215
|
+
return this.configCache.get(name);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* A type containing both a telemetry logger and a configuration provider
|
|
221
|
+
*/
|
|
222
|
+
export interface MonitoringContext<
|
|
223
|
+
L extends ITelemetryBaseLogger = ITelemetryLogger
|
|
224
|
+
> {
|
|
225
|
+
config: IConfigProvider;
|
|
226
|
+
logger: L;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function loggerIsMonitoringContext<L extends ITelemetryBaseLogger = ITelemetryLogger>(
|
|
230
|
+
obj: L): obj is L & MonitoringContext<L> {
|
|
231
|
+
const maybeConfig = obj as Partial<MonitoringContext<L>> | undefined;
|
|
232
|
+
return isConfigProviderBase(maybeConfig?.config) && maybeConfig?.logger !== undefined;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function loggerToMonitoringContext<L extends ITelemetryBaseLogger = ITelemetryLogger>(
|
|
236
|
+
logger: L): MonitoringContext<L> {
|
|
237
|
+
if(loggerIsMonitoringContext<L>(logger)) {
|
|
238
|
+
return logger;
|
|
239
|
+
}
|
|
240
|
+
return mixinMonitoringContext<L>(logger, sessionStorageConfigProvider.value);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function mixinMonitoringContext<L extends ITelemetryBaseLogger = ITelemetryLogger>(
|
|
244
|
+
logger: L, ... configs: (IConfigProviderBase | undefined)[]) {
|
|
245
|
+
if (loggerIsMonitoringContext<L>(logger)) {
|
|
246
|
+
throw new Error("Logger is already a monitoring context");
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* this is the tricky bit we use for now to smuggle monitoring context around.
|
|
250
|
+
* To the logger we mixin both config and itself, so mc.logger === logger as it is self-referential.
|
|
251
|
+
* We then expose it as a Monitoring context, so via types we hide the outer logger methods.
|
|
252
|
+
* To layers that expect just a logger we can pass mc.logger, but this is still a MonitoringContext
|
|
253
|
+
* so if a deeper layer then converts that logger to a monitoring context it can find the smuggled properties
|
|
254
|
+
* of the MonitoringContext and get the config provider.
|
|
255
|
+
*/
|
|
256
|
+
const mc: L & Partial<MonitoringContext<L>> = logger;
|
|
257
|
+
mc.config = new CachedConfigProvider(...configs);
|
|
258
|
+
mc.logger = logger;
|
|
259
|
+
return mc as MonitoringContext<L>;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function isConfigProviderBase(obj: unknown): obj is IConfigProviderBase {
|
|
263
|
+
const maybeConfig = obj as Partial<IConfigProviderBase> | undefined;
|
|
264
|
+
return typeof (maybeConfig?.getRawConfig) === "function";
|
|
265
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -11,3 +11,12 @@ export * from "./logger";
|
|
|
11
11
|
export * from "./mockLogger";
|
|
12
12
|
export * from "./thresholdCounter";
|
|
13
13
|
export * from "./utils";
|
|
14
|
+
export {
|
|
15
|
+
MonitoringContext,
|
|
16
|
+
IConfigProviderBase,
|
|
17
|
+
sessionStorageConfigProvider,
|
|
18
|
+
mixinMonitoringContext,
|
|
19
|
+
IConfigProvider,
|
|
20
|
+
ConfigTypes,
|
|
21
|
+
loggerToMonitoringContext,
|
|
22
|
+
} from "./config";
|
package/src/logger.ts
CHANGED
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
TelemetryEventCategory,
|
|
17
17
|
} from "@fluidframework/common-definitions";
|
|
18
18
|
import { BaseTelemetryNullLogger, performance } from "@fluidframework/common-utils";
|
|
19
|
+
import {
|
|
20
|
+
CachedConfigProvider,
|
|
21
|
+
loggerIsMonitoringContext,
|
|
22
|
+
mixinMonitoringContext,
|
|
23
|
+
} from "./config";
|
|
19
24
|
import {
|
|
20
25
|
isILoggingError,
|
|
21
26
|
extractLogSafeErrorProperties,
|
|
@@ -313,9 +318,17 @@ export class ChildLogger extends TelemetryLogger {
|
|
|
313
318
|
|
|
314
319
|
private constructor(
|
|
315
320
|
protected readonly baseLogger: ITelemetryBaseLogger,
|
|
316
|
-
namespace
|
|
317
|
-
properties
|
|
321
|
+
namespace: string | undefined,
|
|
322
|
+
properties: ITelemetryLoggerPropertyBags | undefined,
|
|
323
|
+
) {
|
|
318
324
|
super(namespace, properties);
|
|
325
|
+
|
|
326
|
+
// propagate the monitoring context
|
|
327
|
+
if(loggerIsMonitoringContext(baseLogger)) {
|
|
328
|
+
mixinMonitoringContext(
|
|
329
|
+
this,
|
|
330
|
+
new CachedConfigProvider(baseLogger.config));
|
|
331
|
+
}
|
|
319
332
|
}
|
|
320
333
|
|
|
321
334
|
/**
|
package/src/packageVersion.ts
CHANGED