-
Notifications
You must be signed in to change notification settings - Fork 239
/
BaseTelemetryPlugin.ts
344 lines (287 loc) · 16.7 KB
/
BaseTelemetryPlugin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
"use strict";
import dynamicProto from "@microsoft/dynamicproto-js";
import { TelemetryUnloadReason } from "../JavaScriptSDK.Enums/TelemetryUnloadReason";
import { TelemetryUpdateReason } from "../JavaScriptSDK.Enums/TelemetryUpdateReason";
import { IAppInsightsCore } from "../JavaScriptSDK.Interfaces/IAppInsightsCore";
import { IConfiguration } from "../JavaScriptSDK.Interfaces/IConfiguration";
import { IDiagnosticLogger } from "../JavaScriptSDK.Interfaces/IDiagnosticLogger";
import { IInstrumentHook } from "../JavaScriptSDK.Interfaces/IInstrumentHooks";
import {
IProcessTelemetryContext, IProcessTelemetryUnloadContext, IProcessTelemetryUpdateContext
} from "../JavaScriptSDK.Interfaces/IProcessTelemetryContext";
import { ITelemetryItem } from "../JavaScriptSDK.Interfaces/ITelemetryItem";
import { IPlugin, ITelemetryPlugin } from "../JavaScriptSDK.Interfaces/ITelemetryPlugin";
import { ITelemetryPluginChain } from "../JavaScriptSDK.Interfaces/ITelemetryPluginChain";
import { ITelemetryUnloadState } from "../JavaScriptSDK.Interfaces/ITelemetryUnloadState";
import { ITelemetryUpdateState } from "../JavaScriptSDK.Interfaces/ITelemetryUpdateState";
import { arrForEach, isArray, isFunction, isNullOrUndefined, proxyFunctionAs, setValue } from "./HelperFuncs";
import { STR_EXTENSION_CONFIG } from "./InternalConstants";
import {
createProcessTelemetryContext, createProcessTelemetryUnloadContext, createProcessTelemetryUpdateContext
} from "./ProcessTelemetryContext";
import { IUnloadHandlerContainer, UnloadHandler, createUnloadHandlerContainer } from "./UnloadHandlerContainer";
let strGetPlugin = "getPlugin";
/**
* BaseTelemetryPlugin provides a basic implementation of the ITelemetryPlugin interface so that plugins
* can avoid implementation the same set of boiler plate code as well as provide a base
* implementation so that new default implementations can be added without breaking all plugins.
*/
export abstract class BaseTelemetryPlugin implements ITelemetryPlugin {
public identifier: string;
public version?: string;
/**
* Holds the core instance that was used during initialization
*/
public core: IAppInsightsCore;
priority: number;
/**
* Call back for telemetry processing before it it is sent
* @param env - This is the current event being reported
* @param itemCtx - This is the context for the current request, ITelemetryPlugin instances
* can optionally use this to access the current core instance or define / pass additional information
* to later plugins (vs appending items to the telemetry item)
*/
public processNext: (env: ITelemetryItem, itemCtx: IProcessTelemetryContext) => void;
/**
* Set next extension for telemetry processing
*/
public setNextPlugin: (next: ITelemetryPlugin | ITelemetryPluginChain) => void;
/**
* Returns the current diagnostic logger that can be used to log issues, if no logger is currently
* assigned a new default one will be created and returned.
*/
public diagLog: (itemCtx?:IProcessTelemetryContext) => IDiagnosticLogger;
/**
* Returns whether the plugin has been initialized
*/
public isInitialized: () => boolean;
/**
* Helper to return the current IProcessTelemetryContext, if the passed argument exists this just
* returns that value (helps with minification for callers), otherwise it will return the configured
* context or a temporary one.
* @param currentCtx - [Optional] The current execution context
*/
protected _getTelCtx: (currentCtx?:IProcessTelemetryContext) => IProcessTelemetryContext;
/**
* Internal helper to allow setting of the internal initialized setting for inherited instances and unit testing
*/
protected setInitialized: (isInitialized: boolean) => void;
/**
* Teardown / Unload hook to allow implementations to perform some additional unload operations before the BaseTelemetryPlugin
* finishes it's removal.
* @param unloadCtx - This is the context that should be used during unloading.
* @param unloadState - The details / state of the unload process, it holds details like whether it should be unloaded synchronously or asynchronously and the reason for the unload.
* @param asyncCallback - An optional callback that the plugin must call if it returns true to inform the caller that it has completed any async unload/teardown operations.
* @returns boolean - true if the plugin has or will call asyncCallback, this allows the plugin to perform any asynchronous operations.
*/
protected _doTeardown?: (unloadCtx?: IProcessTelemetryUnloadContext, unloadState?: ITelemetryUnloadState, asyncCallback?: () => void) => void | boolean;
/**
* Extension hook to allow implementations to perform some additional update operations before the BaseTelemetryPlugin finishes it's removal
* @param updateCtx - This is the context that should be used during updating.
* @param updateState - The details / state of the update process, it holds details like the current and previous configuration.
* @param asyncCallback - An optional callback that the plugin must call if it returns true to inform the caller that it has completed any async update operations.
* @returns boolean - true if the plugin has or will call asyncCallback, this allows the plugin to perform any asynchronous operations.
*/
protected _doUpdate?: (updateCtx?: IProcessTelemetryUpdateContext, updateState?: ITelemetryUpdateState, asyncCallback?: () => void) => void | boolean;
constructor() {
let _self = this; // Setting _self here as it's used outside of the dynamicProto as well
// NOTE!: DON'T set default values here, instead set them in the _initDefaults() function as it is also called during teardown()
let _isinitialized: boolean;
let _rootCtx: IProcessTelemetryContext; // Used as the root context, holding the current config and initialized core
let _nextPlugin: ITelemetryPlugin | ITelemetryPluginChain; // Used for backward compatibility where plugins don't call the main pipeline
let _unloadHandlerContainer: IUnloadHandlerContainer;
let _hooks: IInstrumentHook[];
_initDefaults();
dynamicProto(BaseTelemetryPlugin, _self, (_self) => {
_self.initialize = (config: IConfiguration, core: IAppInsightsCore, extensions: IPlugin[], pluginChain?:ITelemetryPluginChain): void => {
_setDefaults(config, core, pluginChain);
_isinitialized = true;
}
_self.teardown = (unloadCtx?: IProcessTelemetryUnloadContext, unloadState?: ITelemetryUnloadState) => {
// If this plugin has already been torn down (not operational) or is not initialized (core is not set)
// or the core being used for unload was not the same core used for initialization.
let core = _self.core;
if (!core || (unloadCtx && core !== unloadCtx.core())) {
// Do Nothing as either the plugin is not initialized or was not initialized by the current core
return;
}
let result: void | boolean;
let unloadDone = false;
let theUnloadCtx = unloadCtx || createProcessTelemetryUnloadContext(null, core, _nextPlugin && _nextPlugin[strGetPlugin] ? _nextPlugin[strGetPlugin]() : _nextPlugin);
let theUnloadState: ITelemetryUnloadState = unloadState || {
reason: TelemetryUnloadReason.ManualTeardown,
isAsync: false
};
function _unloadCallback() {
if (!unloadDone) {
unloadDone = true;
_unloadHandlerContainer.run(theUnloadCtx, unloadState);
let oldHooks = _hooks;
_hooks = [];
// Remove all instrumentation hooks
arrForEach(oldHooks, (fn) => {
fn.rm();
});
if (result === true) {
theUnloadCtx.processNext(theUnloadState);
}
_initDefaults();
}
}
if (!_self._doTeardown || _self._doTeardown(theUnloadCtx, theUnloadState, _unloadCallback) !== true) {
_unloadCallback();
} else {
// Tell the caller that we will be calling processNext()
result = true;
}
return result;
};
_self.update = (updateCtx: IProcessTelemetryUpdateContext, updateState: ITelemetryUpdateState) => {
// If this plugin has already been torn down (not operational) or is not initialized (core is not set)
// or the core being used for unload was not the same core used for initialization.
let core = _self.core;
if (!core || (updateCtx && core !== updateCtx.core())) {
// Do Nothing
return;
}
let result: void | boolean;
let updateDone = false;
let theUpdateCtx = updateCtx || createProcessTelemetryUpdateContext(null, core, _nextPlugin && _nextPlugin[strGetPlugin] ? _nextPlugin[strGetPlugin]() : _nextPlugin);
let theUpdateState: ITelemetryUpdateState = updateState || {
reason: TelemetryUpdateReason.Unknown
};
function _updateCallback() {
if (!updateDone) {
updateDone = true;
_setDefaults(theUpdateCtx.getCfg(), theUpdateCtx.core(), theUpdateCtx.getNext());
}
}
if (!_self._doUpdate || _self._doUpdate(theUpdateCtx, theUpdateState, _updateCallback) !== true) {
_updateCallback();
} else {
result = true;
}
return result;
};
_self._addHook = (hooks: IInstrumentHook | IInstrumentHook[]) => {
if (hooks) {
if (isArray(hooks)) {
_hooks = _hooks.concat(hooks);
} else {
_hooks.push(hooks);
}
}
};
proxyFunctionAs(_self, "_addUnloadCb", () => _unloadHandlerContainer, "add");
});
// These are added after the dynamicProto so that are not moved to the prototype
_self.diagLog = (itemCtx:IProcessTelemetryContext): IDiagnosticLogger => {
return _getTelCtx(itemCtx).diagLog();
}
_self.isInitialized = () => {
return _isinitialized;
}
_self.setInitialized = (isInitialized: boolean):void => {
_isinitialized = isInitialized;
}
// _self.getNextPlugin = () => DO NOT IMPLEMENT
// Sub-classes of this base class *should* not be relying on this value and instead
// should use processNext() function. If you require access to the plugin use the
// IProcessTelemetryContext.getNext().getPlugin() while in the pipeline, Note getNext() may return null.
_self.setNextPlugin = (next: ITelemetryPlugin | ITelemetryPluginChain) => {
_nextPlugin = next;
};
_self.processNext = (env: ITelemetryItem, itemCtx: IProcessTelemetryContext) => {
if (itemCtx) {
// Normal core execution sequence
itemCtx.processNext(env);
} else if (_nextPlugin && isFunction(_nextPlugin.processTelemetry)) {
// Looks like backward compatibility or out of band processing. And as it looks
// like a ITelemetryPlugin or ITelemetryPluginChain, just call processTelemetry
_nextPlugin.processTelemetry(env, null);
}
};
_self._getTelCtx = _getTelCtx;
function _getTelCtx(currentCtx: IProcessTelemetryContext = null) {
let itemCtx:IProcessTelemetryContext = currentCtx;
if (!itemCtx) {
let rootCtx = _rootCtx || createProcessTelemetryContext(null, {}, _self.core);
// tslint:disable-next-line: prefer-conditional-expression
if (_nextPlugin && _nextPlugin[strGetPlugin]) {
// Looks like a chain object
itemCtx = rootCtx.createNew(null, _nextPlugin[strGetPlugin]);
} else {
itemCtx = rootCtx.createNew(null, _nextPlugin as ITelemetryPlugin);
}
}
return itemCtx;
}
function _setDefaults(config: IConfiguration, core: IAppInsightsCore, pluginChain: ITelemetryPluginChain) {
if (config) {
// Make sure the extensionConfig exists
setValue(config, STR_EXTENSION_CONFIG, [], null, isNullOrUndefined);
}
if (!pluginChain && core) {
// Get the first plugin from the core
pluginChain = core.getProcessTelContext().getNext();
}
let nextPlugin: IPlugin = _nextPlugin as IPlugin;
if (_nextPlugin && _nextPlugin[strGetPlugin]) {
// If it looks like a proxy/chain then get the plugin
nextPlugin = _nextPlugin[strGetPlugin]();
}
// Support legacy plugins where core was defined as a property
_self.core = core;
_rootCtx = createProcessTelemetryContext(pluginChain, config, core, nextPlugin);
}
function _initDefaults() {
_isinitialized = false;
_self.core = null;
_rootCtx = null;
_nextPlugin = null;
_hooks = [];
_unloadHandlerContainer = createUnloadHandlerContainer();
}
}
public initialize(config: IConfiguration, core: IAppInsightsCore, extensions: IPlugin[], pluginChain?:ITelemetryPluginChain): void {
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
}
/**
* Tear down the plugin and remove any hooked value, the plugin should be removed so that it is no longer initialized and
* therefore could be re-initialized after being torn down. The plugin should ensure that once this has been called any further
* processTelemetry calls are ignored and it just calls the processNext() with the provided context.
* @param unloadCtx - This is the context that should be used during unloading.
* @param unloadState - The details / state of the unload process, it holds details like whether it should be unloaded synchronously or asynchronously and the reason for the unload.
* @returns boolean - true if the plugin has or will call processNext(), this for backward compatibility as previously teardown was synchronous and returned nothing.
*/
public teardown(unloadCtx?: IProcessTelemetryUnloadContext, unloadState?: ITelemetryUnloadState): void | boolean {
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
return false;
}
public abstract processTelemetry(env: ITelemetryItem, itemCtx?: IProcessTelemetryContext): void;
/**
* The the plugin should re-evaluate configuration and update any cached configuration settings.
* @param updateCtx - This is the context that should be used during updating.
* @param updateState - The details / state of the update process, it holds details like the current and previous configuration.
* @returns boolean - true if the plugin has or will call updateCtx.processNext(), this allows the plugin to perform any asynchronous operations.
*/
public update(updateCtx: IProcessTelemetryUpdateContext, updateState: ITelemetryUpdateState): void | boolean{
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
}
/**
* Add an unload handler that will be called when the SDK is being unloaded
* @param handler - the handler
*/
protected _addUnloadCb(handler: UnloadHandler): void {
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
}
/**
* Add this hook so that it is automatically removed during unloading
* @param hooks - The single hook or an array of IInstrumentHook objects
*/
protected _addHook(hooks: IInstrumentHook | IInstrumentHook[]): void {
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
}
}