Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a way to enrich dependencies logs with context at the beginning of api call #1624

Merged
merged 11 commits into from
Aug 6, 2021
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ Most configuration fields are named such that they can be defaulted to falsey. A
| disableAjaxTracking | boolean | false | If true, Ajax calls are not autocollected. Default is false. |
| disableFetchTracking | boolean | true | If true, Fetch requests are not autocollected. Default is true |
| excludeRequestFromAutoTrackingPatterns | string[] \| RegExp[] | undefined | Provide a way to exclude specific route from automatic tracking for XMLHttpRequest or Fetch request. If defined, for an ajax / fetch request that the request url matches with the regex patterns, auto tracking is turned off. Default is undefined. |
| addAjaxContext | () => {[key: string]: any} | undefined | Provide a way to enrich dependencies logs with context at the beginning of ajax call. Default is undefined. |
| addFetchContext | () => {[key: string]: any} | undefined | Provide a way to enrich dependencies logs with context at the beginning of fetch call. Default is undefined. |
| overridePageViewDuration | boolean | false | If true, default behavior of trackPageView is changed to record end of page view duration interval when trackPageView is called. If false and no custom duration is provided to trackPageView, the page view performance is calculated using the navigation timing API. Default is false. |
| maxAjaxCallsPerView | numeric | 500 | Default 500 - controls how many ajax calls will be monitored per page view. Set to -1 to monitor all (unlimited) ajax calls on the page. |
| disableDataLossAnalysis | boolean | true | If false, internal telemetry sender buffers will be checked at startup for items not yet sent. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,45 @@ export class AjaxTests extends AITestClass {
}
});

this.testCase({
name: "Ajax: add context into custom dimension with call back configuration on AI initialization.",
test: () => {
this._ajax = new AjaxMonitor();
let dependencyFields = hookTrackDependencyInternal(this._ajax);
let appInsightsCore = new AppInsightsCore();
var trackStub = this.sandbox.stub(appInsightsCore, "track");

let coreConfig: IConfiguration & IConfig = {
instrumentationKey: "",
disableAjaxTracking: false,
addAjaxContext: (xhr?: XMLHttpRequest) => {
return {
test: "ajax context",
xhrStatus: xhr.status
}
}
};
appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);

// act
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://microsoft.com");
xhr.send();

// Emulate response
(<any>xhr).respond(200, {}, "");

Assert.equal(1, dependencyFields.length, "trackDependencyDataInternal was called");

// assert
Assert.ok(trackStub.calledOnce, "track is called");
let data = trackStub.args[0][0].baseData;
Assert.equal("Ajax", data.type, "request is Ajax type");
Assert.equal("ajax context", data.properties.test, "xhr request's request context is added when customer configures addAjaxContext.");
Assert.equal(200, data.properties.xhrStatus, "xhr object properties are captured");
}
});

this.testCase({
name: "Ajax: xhr request header is tracked as part C data when enableRequestHeaderTracking flag is true",
test: () => {
Expand Down Expand Up @@ -483,6 +522,63 @@ export class AjaxTests extends AITestClass {
}]
});

this.testCaseAsync({
name: "Fetch: add context into custom dimension with call back configuration on AI initialization.",
stepDelay: 10,
autoComplete: false,
timeOut: 10000,
steps: [ (testContext) => {
hookFetch((resolve) => {
AITestClass.orgSetTimeout(function() {
resolve({
headers: new Headers(),
ok: true,
body: null,
bodyUsed: false,
redirected: false,
status: 200,
statusText: "Hello",
trailer: null,
type: "basic",
url: "https://httpbin.org/status/200"
});
}, 0);
});

this._ajax = new AjaxMonitor();
let dependencyFields = hookTrackDependencyInternal(this._ajax);
let appInsightsCore = new AppInsightsCore();
let coreConfig = {
instrumentationKey: "",
disableFetchTracking: false,
addFetchContext: (input?: Request | Response | string) => {
return {
test: "Fetch context",
fetchRequestUrl: (input as Request).url
}
}
};
appInsightsCore.initialize(coreConfig, [this._ajax, new TestChannelPlugin()]);
let fetchSpy = this.sandbox.spy(appInsightsCore, "track")

// Act
Assert.ok(fetchSpy.notCalled, "No fetch called yet");
fetch("https://httpbin.org/status/200", {method: "post", [DisabledPropertyName]: false}).then(() => {
// assert
Assert.ok(fetchSpy.calledOnce, "track is called");
let data = fetchSpy.args[0][0].baseData;
Assert.equal("Fetch", data.type, "request is Fetch type");
Assert.equal(1, dependencyFields.length, "trackDependencyDataInternal was called");
Assert.equal("Fetch context", data.properties.test, "Fetch request's request context is added when customer configures addFetchContext.");
Assert.equal("https://httpbin.org/status/200", data.properties.fetchRequestUrl, "Fetch input is captured.");
testContext.testDone();
}, () => {
Assert.ok(false, "fetch failed!");
testContext.testDone();
});
}]
});

this.testCaseAsync({
name: "Fetch: fetch gets instrumented",
stepDelay: 10,
Expand Down
25 changes: 22 additions & 3 deletions extensions/applicationinsights-dependencies-js/src/ajax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
isNullOrUndefined, arrForEach, isString, strTrim, isFunction, LoggingSeverity, _InternalMessageId,
IAppInsightsCore, BaseTelemetryPlugin, ITelemetryPluginChain, IConfiguration, IPlugin, ITelemetryItem, IProcessTelemetryContext,
getLocation, getGlobal, strUndefined, strPrototype, IInstrumentCallDetails, InstrumentFunc, InstrumentProto, getPerformance,
IInstrumentHooksCallbacks, IInstrumentHook, objForEachKey, generateW3CId, getIEVersion, dumpObj,objKeys
IInstrumentHooksCallbacks, IInstrumentHook, objForEachKey, generateW3CId, getIEVersion, dumpObj,objKeys, ICustomProperties
} from '@microsoft/applicationinsights-core-js';
import { ajaxRecord, IAjaxRecordResponse } from './ajaxRecord';
import { EventHelper } from './ajaxUtils';
Expand Down Expand Up @@ -176,7 +176,9 @@ export class AjaxMonitor extends BaseTelemetryPlugin implements IDependenciesPlu
ignoreHeaders:[
"Authorization",
"X-API-Key",
"WWW-Authenticate"]
"WWW-Authenticate"],
addAjaxContext: undefined,
addFetchContext: undefined
}
return config;
}
Expand Down Expand Up @@ -214,6 +216,8 @@ export class AjaxMonitor extends BaseTelemetryPlugin implements IDependenciesPlu
let _hooks:IInstrumentHook[] = [];
let _disabledUrls:any = {};
let _excludeRequestFromAutoTrackingPatterns: string[] | RegExp[];
let _addAjaxContext: (xhr?: XMLHttpRequest) => ICustomProperties;
let _addFetchContext: (input?: Request | Response | string) => ICustomProperties;

dynamicProto(AjaxMonitor, this, (_self, base) => {
_self.initialize = (config: IConfiguration & IConfig, core: IAppInsightsCore, extensions: IPlugin[], pluginChain?:ITelemetryPluginChain) => {
Expand Down Expand Up @@ -724,7 +728,15 @@ export class AjaxMonitor extends BaseTelemetryPlugin implements IDependenciesPlu
return ajaxResponse;
});

let properties;
if (!!_addAjaxContext) {
properties = _addAjaxContext(xhr);
}

if (dependency) {
if (properties !== undefined) {
dependency.properties = {...dependency.properties, ...properties};
}
_self[strTrackDependencyDataInternal](dependency);
} else {
_reportXhrError(null, {
Expand Down Expand Up @@ -923,7 +935,14 @@ export class AjaxMonitor extends BaseTelemetryPlugin implements IDependenciesPlu

_findPerfResourceEntry("fetch", ajaxData, () => {
const dependency = ajaxData.CreateTrackItem("Fetch", _enableRequestHeaderTracking, getResponse);
let properties;
if (!!_addFetchContext) {
properties = _addFetchContext(input);
}
if (dependency) {
if (properties !== undefined) {
dependency.properties = {...dependency.properties, ...properties};
}
_self[strTrackDependencyDataInternal](dependency);
} else {
_reportFetchError(_InternalMessageId.FailedMonitorAjaxDur, null,
Expand Down Expand Up @@ -981,7 +1000,7 @@ export class AjaxMonitor extends BaseTelemetryPlugin implements IDependenciesPlu
}

/**
* Protected function to allow sub classes the chance to add additional properties to the delendency event
* Protected function to allow sub classes the chance to add additional properties to the dependency event
* before it's sent. This function calls track, so sub-classes must call this function after they have
* populated their properties.
* @param dependencyData dependency data object
Expand Down
14 changes: 13 additions & 1 deletion shared/AppInsightsCommon/src/Interfaces/IConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { IConfiguration, ICookieMgrConfig, isNullOrUndefined } from '@microsoft/applicationinsights-core-js';
import { IConfiguration, ICookieMgrConfig, isNullOrUndefined, ICustomProperties } from '@microsoft/applicationinsights-core-js';
import { DistributedTracingModes } from '../Enums';

/**
Expand Down Expand Up @@ -126,6 +126,18 @@ export interface IConfig {
*/
excludeRequestFromAutoTrackingPatterns?: string[] | RegExp[];

/**
* Provide a way to enrich dependencies logs with context at the beginning of ajax call.
* Default is undefined.
*/
addAjaxContext?: (xhr?: XMLHttpRequest) => ICustomProperties;

/**
* Provide a way to enrich dependencies logs with context at the beginning of fetch call.
* Default is undefined.
*/
addFetchContext?: (input?: Request | Response | string) => ICustomProperties;

/**
* @description If true, default behavior of trackPageView is changed to record end of page view duration interval when trackPageView is called. If false and no custom duration is provided to trackPageView, the page view performance is calculated using the navigation timing API. Default is false
* @type {boolean}
Expand Down
13 changes: 13 additions & 0 deletions shared/AppInsightsCommon/src/Interfaces/ICorrelationConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { ICustomProperties } from '@microsoft/applicationinsights-core-js';
import { DistributedTracingModes } from '../Enums';

export interface ICorrelationConfig {
Expand Down Expand Up @@ -52,4 +53,16 @@ export interface ICorrelationConfig {
* Default is undefined.
*/
excludeRequestFromAutoTrackingPatterns?: string[] | RegExp[];

/**
* Provide a way to enrich dependencies logs with context at the beginning of ajax call.
* Default is undefined.
*/
addAjaxContext?: (xhr?: XMLHttpRequest) => ICustomProperties;
xiao-lix marked this conversation as resolved.
Show resolved Hide resolved

/**
* Provide a way to enrich dependencies logs with context at the beginning of fetch call.
* Default is undefined.
*/
addFetchContext?: (input?: Request | Response | string) => ICustomProperties;
}