Skip to content

Commit

Permalink
Add Send Request notification (#1278)
Browse files Browse the repository at this point in the history
* Add Send Request notification
* Update channels/applicationinsights-channel-js/Tests/Sender.tests.ts
  • Loading branch information
MSNev committed Jun 2, 2020
1 parent 712f13e commit 0fe9ff9
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 36 deletions.
5 changes: 3 additions & 2 deletions AISKULight/index.ts
Expand Up @@ -8,7 +8,8 @@ import {
IAppInsightsCore,
_InternalMessageId,
CoreUtils,
ITelemetryItem
ITelemetryItem,
SendRequestReason
} from "@microsoft/applicationinsights-core-js";
import { IConfig } from "@microsoft/applicationinsights-common";
import { Sender } from "@microsoft/applicationinsights-channel-js";
Expand Down Expand Up @@ -83,7 +84,7 @@ export class ApplicationInsights {
CoreUtils.arrForEach(controls, plugin => {
async
? (plugin as Sender).flush()
: (plugin as Sender).triggerSend(async);
: (plugin as Sender).triggerSend(async, null, SendRequestReason.ManualFlush);
});
});
}
Expand Down
106 changes: 105 additions & 1 deletion channels/applicationinsights-channel-js/Tests/Sender.tests.ts
Expand Up @@ -3,7 +3,7 @@ import { Sender } from "../src/Sender";
import { Offline } from '../src/Offline';
import { EnvelopeCreator } from '../src/EnvelopeCreator';
import { Exception, CtxTagKeys, Util } from "@microsoft/applicationinsights-common";
import { ITelemetryItem, AppInsightsCore, ITelemetryPlugin, DiagnosticLogger } from "@microsoft/applicationinsights-core-js";
import { ITelemetryItem, AppInsightsCore, ITelemetryPlugin, DiagnosticLogger, NotificationManager, SendRequestReason } from "@microsoft/applicationinsights-core-js";

export class SenderTests extends TestClass {
private _sender: Sender;
Expand Down Expand Up @@ -837,5 +837,109 @@ export class SenderTests extends TestClass {
Assert.equal("Microsoft.ApplicationInsights.iKey.Pageview", appInsightsEnvelope.name);
}
});

this.testCase({
name: "Channel Config: Notification is sent when requests are being sent when requests exceed max batch size",
test: () => {
let sendNotifications = [];
let notificationManager = new NotificationManager();
notificationManager.addNotificationListener({
eventsSendRequest: (sendReason: number, isAsync?: boolean) => {
sendNotifications.push({
sendReason,
isAsync
});
}
});

this._sender.initialize(
{
instrumentationKey: 'abc',
maxBatchInterval: 123,
endpointUrl: 'https://example.com',
maxBatchSizeInBytes: 100,
extensionConfig: {
NotificationManager: notificationManager,
[this._sender.identifier]: {
maxBatchSizeInBytes: 100
}
}

}, new AppInsightsCore(), []
);

const loggerSpy = this.sandbox.stub(this._sender, "_setupTimer");
const telemetryItem: ITelemetryItem = {
name: 'fake item',
iKey: 'iKey',
baseType: 'some type',
baseData: {}
};
try {
this._sender.processTelemetry(telemetryItem, null);
} catch(e) {
Assert.ok(false);
}


Assert.ok(loggerSpy.calledOnce);
this.clock.tick(1);
Assert.ok(sendNotifications.length === 1);
Assert.ok(sendNotifications[0].sendReason === SendRequestReason.MaxBatchSize);
}
});

this.testCase({
name: "Channel Config: Notification is sent when requests are being sent with manual flush",
test: () => {
let sendNotifications = [];
let notificationManager = new NotificationManager();
notificationManager.addNotificationListener({
eventsSendRequest: (sendReason: number, isAsync?: boolean) => {
sendNotifications.push({
sendReason,
isAsync
});
}
});

this._sender.initialize(
{
instrumentationKey: 'abc',
maxBatchInterval: 123,
endpointUrl: 'https://example.com',
extensionConfig: {
NotificationManager: notificationManager
}

}, new AppInsightsCore(), []
);

const loggerSpy = this.sandbox.stub(this._sender, "_setupTimer");
const telemetryItem: ITelemetryItem = {
name: 'fake item',
iKey: 'iKey',
baseType: 'some type',
baseData: {}
};
try {
this._sender.processTelemetry(telemetryItem, null);
} catch(e) {
Assert.ok(false);
}

Assert.ok(loggerSpy.calledOnce);
Assert.equal(0, sendNotifications.length);

this._sender.flush();
Assert.equal(0, sendNotifications.length);

this.clock.tick(1);

Assert.equal(1, sendNotifications.length);
Assert.equal(SendRequestReason.ManualFlush, sendNotifications[0].sendReason);
}
});

}
}
33 changes: 26 additions & 7 deletions channels/applicationinsights-channel-js/src/Sender.ts
Expand Up @@ -20,7 +20,8 @@ import {
import {
ITelemetryItem, IProcessTelemetryContext, IConfiguration, CoreUtils,
_InternalMessageId, LoggingSeverity, IDiagnosticLogger, IAppInsightsCore, IPlugin,
getWindow, getNavigator, getJSON, BaseTelemetryPlugin, ITelemetryPluginChain
getWindow, getNavigator, getJSON, BaseTelemetryPlugin, ITelemetryPluginChain, INotificationManager,
SendRequestReason
} from '@microsoft/applicationinsights-core-js';
import { Offline } from './Offline';
import { Sample } from './TelemetryProcessors/Sample'
Expand Down Expand Up @@ -162,6 +163,8 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI {

private _serializer: Serializer;

private _notificationManager: INotificationManager | undefined;

public pause(): void {
throw new Error("Method not implemented.");
}
Expand All @@ -172,7 +175,7 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI {

public flush() {
try {
this.triggerSend();
this.triggerSend(true, null, SendRequestReason.ManualFlush);
} catch (e) {
this.diagLog().throwInternal(LoggingSeverity.CRITICAL,
_InternalMessageId.FlushFailed,
Expand All @@ -184,7 +187,7 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI {
public onunloadFlush() {
if ((this._senderConfig.onunloadDisableBeacon() === false || this._senderConfig.isBeaconApiDisabled() === false) && Util.IsBeaconApiSupported()) {
try {
this.triggerSend(true, this._beaconSender);
this.triggerSend(true, this._beaconSender, SendRequestReason.Unload);
} catch (e) {
this.diagLog().throwInternal(LoggingSeverity.CRITICAL,
_InternalMessageId.FailedToSendQueuedTelemetry,
Expand All @@ -205,7 +208,7 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI {
let ctx = this._getTelCtx();
let identifier = this.identifier;
this._serializer = new Serializer(core.logger);

this._notificationManager = ((config||{}).extensionConfig||{}).NotificationManager
this._consecutiveErrors = 0;
this._retryAt = null;
this._lastSend = 0;
Expand Down Expand Up @@ -317,7 +320,7 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI {
const batch = this._buffer.batchPayloads(bufferPayload);

if (batch && (batch.length + payload.length > this._senderConfig.maxBatchSizeInBytes())) {
this.triggerSend();
this.triggerSend(true, null, SendRequestReason.MaxBatchSize);
}

// enqueue the payload
Expand Down Expand Up @@ -396,14 +399,16 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI {
* @param async {boolean} - Indicates if the events should be sent asynchronously
* @param forcedSender {SenderFunction} - Indicates the forcedSender, undefined if not passed
*/
public triggerSend(async = true, forcedSender?: SenderFunction) {
public triggerSend(async = true, forcedSender?: SenderFunction, sendReason?: SendRequestReason) {
try {
// Send data only if disableTelemetry is false
if (!this._senderConfig.disableTelemetry()) {

if (this._buffer.count() > 0) {
const payload = this._buffer.getItems();

this._notifySendRequest(sendReason||SendRequestReason.Undefined, async);

// invoke send
if (forcedSender) {
forcedSender.call(this, payload, async);
Expand Down Expand Up @@ -658,7 +663,7 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI {
const timerValue = Math.max(this._senderConfig.maxBatchInterval(), retryInterval);

this._timeoutHandle = setTimeout(() => {
this.triggerSend();
this.triggerSend(true, null, SendRequestReason.NormalSchedule);
}, timerValue);
}
}
Expand Down Expand Up @@ -728,4 +733,18 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI {

return message;
}

private _notifySendRequest(sendRequest: SendRequestReason, isAsync: boolean) {
let manager = this._notificationManager;
if (manager && manager.eventsSendRequest) {
try {
manager.eventsSendRequest(sendRequest, isAsync);
} catch (e) {
this.diagLog().throwInternal(LoggingSeverity.CRITICAL,
_InternalMessageId.NotificationException,
"send request notification failed: " + Util.getExceptionName(e),
{ exception: Util.dump(e) });
}
}
}
}
48 changes: 24 additions & 24 deletions common/config/rush/npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -93,6 +93,7 @@ export const _InternalMessageId = {
FailedMonitorAjaxSetRequestHeader: 71,
SendBrowserInfoOnUserInit: 72,
PluginException: 73,
NotificationException: 74,
SnippetScriptLoadFailure: 99
};
export type _InternalMessageId = number | typeof _InternalMessageId;
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
/**
* The EventsDiscardedReason enumeration contains a set of values that specify the reason for discarding an event.
*/
export const enum SendRequestReason {
/**
* No specific reason was specified
*/
Undefined = 0,

/**
* Events are being sent based on the normal event schedule / timer.
*/
NormalSchedule = 1,

/**
* A manual flush request was received
*/
ManualFlush = 1,

/**
* Unload event is being processed
*/
Unload = 2,

/**
* The event(s) being sent are sync events
*/
SyncEvent = 3,

/**
* The Channel was resumed
*/
Resumed = 4,

/**
* Maximum batch size would be exceeded
*/
MaxBatchSize = 10,

/**
* The Maximum number of events have already been queued
*/
MaxQueuedEvents = 20
};

0 comments on commit 0fe9ff9

Please sign in to comment.