Skip to content
48 changes: 40 additions & 8 deletions AISKU/src/AISku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import {
IAutoExceptionTelemetry, IChannelControls, IConfig, IConfigDefaults, IConfiguration, ICookieMgr, ICustomProperties, IDependencyTelemetry,
IDiagnosticLogger, IDistributedTraceContext, IDynamicConfigHandler, IEventTelemetry, IExceptionTelemetry, ILoadedPlugin,
IMetricTelemetry, INotificationManager, IOTelApi, IOTelSpanOptions, IPageViewPerformanceTelemetry, IPageViewTelemetry, IPlugin,
IReadableSpan, IRequestHeaders, ISpanScope, ITelemetryContext as Common_ITelemetryContext, ITelemetryInitializerHandler, ITelemetryItem,
ITelemetryPlugin, ITelemetryUnloadState, IThrottleInterval, IThrottleLimit, IThrottleMgrConfig, ITraceApi, ITraceProvider,
ITraceTelemetry, IUnloadHook, OTelTimeInput, PropertiesPluginIdentifier, ThrottleMgr, UnloadHandler, WatcherFunction,
_eInternalMessageId, _throwInternal, addPageHideEventListener, addPageUnloadEventListener, cfgDfMerge, cfgDfValidate,
createDynamicConfig, createOTelApi, createProcessTelemetryContext, createTraceProvider, createUniqueNamespace, doPerf, eLoggingSeverity,
hasDocument, hasWindow, isArray, isFeatureEnabled, isFunction, isNullOrUndefined, isReactNative, isString, mergeEvtNamespace,
onConfigChange, parseConnectionString, proxyAssign, proxyFunctions, removePageHideEventListener, removePageUnloadEventListener, useSpan
IReadableSpan, IRequestHeaders, ISdkStatsNotifCbk, ISpanScope, ITelemetryContext as Common_ITelemetryContext,
ITelemetryInitializerHandler, ITelemetryItem, ITelemetryPlugin, ITelemetryUnloadState, IThrottleInterval, IThrottleLimit,
IThrottleMgrConfig, ITraceApi, ITraceProvider, ITraceTelemetry, IUnloadHook, OTelTimeInput, PropertiesPluginIdentifier, ThrottleMgr,
UnloadHandler, WatcherFunction, _eInternalMessageId, _throwInternal, addPageHideEventListener, addPageUnloadEventListener, cfgDfMerge,
cfgDfValidate, createDynamicConfig, createOTelApi, createProcessTelemetryContext, createSdkStatsNotifCbk, createTraceProvider,
createUniqueNamespace, doPerf, eLoggingSeverity, hasDocument, hasWindow, isArray, isFeatureEnabled, isFunction, isNullOrUndefined,
isReactNative, isString, mergeEvtNamespace, onConfigChange, parseConnectionString, proxyAssign, proxyFunctions,
removePageHideEventListener, removePageUnloadEventListener, useSpan
} from "@microsoft/applicationinsights-core-js";
import {
AjaxPlugin as DependenciesPlugin, DependencyInitializerFunction, DependencyListenerFunction, IDependencyInitializerHandler,
Expand Down Expand Up @@ -64,6 +65,9 @@ const IKEY_USAGE = "iKeyUsage";
const CDN_USAGE = "CdnUsage";
const SDK_LOADER_VER = "SdkLoaderVer";
const ZIP_PAYLOAD = "zipPayload";
const SDK_STATS = "SdkStats";
const SDK_STATS_VERSION = "#version#";
const SDK_STATS_FLUSH_INTERVAL = 900000; // 15 minutes in ms

const default_limit = {
samplingRate: 100,
Expand Down Expand Up @@ -93,7 +97,8 @@ const defaultConfigValues: IConfigDefaults<IConfiguration & IConfig> = {
[IKEY_USAGE]: {mode: FeatureOptInMode.enable}, //for versions after 3.1.2 (>= 3.2.0)
[CDN_USAGE]: {mode: FeatureOptInMode.disable},
[SDK_LOADER_VER]: {mode: FeatureOptInMode.disable},
[ZIP_PAYLOAD]: {mode: FeatureOptInMode.none}
[ZIP_PAYLOAD]: {mode: FeatureOptInMode.none},
[SDK_STATS]: {mode: FeatureOptInMode.enable}
},
throttleMgrCfg: cfgDfMerge<{[key:number]: IThrottleMgrConfig}>(
{
Expand Down Expand Up @@ -196,6 +201,7 @@ export class AppInsightsSku implements IApplicationInsights<IConfiguration & ICo
let _cdnSentMessage: boolean;
let _sdkVerSentMessage: boolean;
let _otelApi: ICachedValue<IOTelApi>;
let _sdkStatsListener: ISdkStatsNotifCbk;

dynamicProto(AppInsightsSku, this, (_self) => {
_initDefaults();
Expand Down Expand Up @@ -390,6 +396,22 @@ export class AppInsightsSku implements IApplicationInsights<IConfiguration & ICo
// initialize core
_core.initialize(_config, [ _sender, properties, dependencies, _analyticsPlugin, _cfgSyncPlugin], logger, notificationManager);

// Register SDK Stats notification listener (on by default)
if (isFeatureEnabled(SDK_STATS, _config, true)) {
_sdkStatsListener = createSdkStatsNotifCbk({
trk: function (item: ITelemetryItem) {
_core.track(item);
},
lang: "JavaScript",
ver: SDK_STATS_VERSION,
int: SDK_STATS_FLUSH_INTERVAL,
fnFlush: function () {
_self.onunloadFlush(false);
}
});
_core.addNotificationListener(_sdkStatsListener);
}

// Initialize the initial OTel API
_otelApi = _initOTel(_self, "aisku", _onEnd, _onException);

Expand Down Expand Up @@ -596,6 +618,15 @@ export class AppInsightsSku implements IApplicationInsights<IConfiguration & ICo

_removePageEventHandlers();

// Unload SDK Stats listener BEFORE core.unload() tears down the pipeline.
// unload() calls core.track() to enqueue the accumulated metrics,
// then calls fnFlush to flush the channel so they actually get sent.
if (_sdkStatsListener && _core) {
_sdkStatsListener.unload();
_core.removeNotificationListener(_sdkStatsListener);
_sdkStatsListener = null;
}

_core.unload && _core.unload(isAsync, _unloadCallback, cbTimeout);

return result;
Expand Down Expand Up @@ -710,6 +741,7 @@ export class AppInsightsSku implements IApplicationInsights<IConfiguration & ICo
_iKeySentMessage = false;
_cdnSentMessage = false;
_sdkVerSentMessage = false;
_sdkStatsListener = null;
_cfgSyncPlugin = new CfgSyncPlugin();
}

Expand Down
5 changes: 5 additions & 0 deletions channels/applicationinsights-channel-js/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export interface IInternalStorageItem {
* total retry count
*/
cnt?: number;
/**
* baseType of the original telemetry item, used for SDK stats telemetry_type mapping.
* @since 3.3.6
*/
bT?: string;
}

export interface ISenderConfig {
Expand Down
58 changes: 57 additions & 1 deletion channels/applicationinsights-channel-js/src/Sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,8 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls {
_checkMaxSize(payload);
let payloadItem = {
item: payload,
cnt: 0 // inital cnt will always be 0
cnt: 0, // inital cnt will always be 0
bT: telemetryItem.baseType // store baseType for SDK stats telemetry_type mapping
} as IInternalStorageItem;

// enqueue the payload
Expand Down Expand Up @@ -807,6 +808,15 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls {
{ message });

_self._buffer && _self._buffer.clearSent(payload);

// Notify listeners of discarded events
let mgr = _getNotifyMgr();
if (mgr) {
let items = _extractTelemetryItems(payload);
if (items) {
mgr.eventsDiscarded(items, 1 /* NonRetryableStatus */);
}
}
}
/**
* partial success handler
Expand Down Expand Up @@ -852,6 +862,15 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls {
*/
function _onSuccess(payload: IInternalStorageItem[], countOfItemsInPayload: number) {
_self._buffer && _self._buffer.clearSent(payload);

// Notify listeners of successful send
let mgr = _getNotifyMgr();
if (mgr) {
let items = _extractTelemetryItems(payload);
if (items) {
mgr.eventsSent(items);
}
}
}


Expand Down Expand Up @@ -1124,6 +1143,9 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls {
if (!_isRetryDisabled) {
const offlineBackOffMultiplier = 10; // arbritrary number
_resendPayload(payload, offlineBackOffMultiplier);

// Notify listeners of retry
_notifyRetry(payload, status);

_throwInternal(_self.diagLog(),
eLoggingSeverity.WARNING,
Expand All @@ -1133,6 +1155,10 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls {
}
if (!_isRetryDisabled && _isRetriable(status)) {
_resendPayload(payload);

// Notify listeners of retry
_notifyRetry(payload, status);

_throwInternal(_self.diagLog(),
eLoggingSeverity.WARNING,
_eInternalMessageId.TransmissionFailed, ". " +
Expand Down Expand Up @@ -1388,6 +1414,36 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControls {
}
}

/**
* Extracts minimal ITelemetryItem objects from IInternalStorageItem[] for notification dispatch.
* Uses the stored baseType (bT) to reconstruct telemetry items.
*/
function _extractTelemetryItems(payload: IInternalStorageItem[]): ITelemetryItem[] {
if (payload && payload.length) {
let items: ITelemetryItem[] = [];
arrForEach(payload, (p) => {
if (p) {
items.push({ name: "", baseType: p.bT || "EventData" } as ITelemetryItem);
}
});
return items.length ? items : null;
}
return null;
}

/**
* Notify listeners of retry events.
*/
function _notifyRetry(payload: IInternalStorageItem[], statusCode: number) {
let mgr = _getNotifyMgr();
if (mgr && mgr.eventsRetry) {
let items = _extractTelemetryItems(payload);
if (items) {
mgr.eventsRetry(items, statusCode);
}
}
}



/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const STR_PRIORITY = "priority";
export const STR_EVENTS_SENT = "eventsSent";
export const STR_EVENTS_DISCARDED = "eventsDiscarded";
export const STR_EVENTS_SEND_REQUEST = "eventsSendRequest";
export const STR_EVENTS_RETRY = "eventsRetry";
export const STR_PERF_EVENT = "perfEvent";
export const STR_OFFLINE_STORE = "offlineEventsStored";
export const STR_OFFLINE_SENT = "offlineBatchSent";
Expand Down
23 changes: 22 additions & 1 deletion shared/AppInsightsCore/src/core/NotificationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { IPromise, createAllPromise, createPromise, doAwaitResponse } from "@nev
import { ITimerHandler, arrForEach, arrIndexOf, objDefine, safe, scheduleTimeout } from "@nevware21/ts-utils";
import { createDynamicConfig } from "../config/DynamicConfig";
import {
STR_EVENTS_DISCARDED, STR_EVENTS_SEND_REQUEST, STR_EVENTS_SENT, STR_OFFLINE_DROP, STR_OFFLINE_SENT, STR_OFFLINE_STORE, STR_PERF_EVENT
STR_EVENTS_DISCARDED, STR_EVENTS_RETRY, STR_EVENTS_SEND_REQUEST, STR_EVENTS_SENT, STR_OFFLINE_DROP, STR_OFFLINE_SENT, STR_OFFLINE_STORE,
STR_PERF_EVENT
} from "../constants/InternalConstants";
import { IConfiguration } from "../interfaces/ai/IConfiguration";
import { INotificationListener } from "../interfaces/ai/INotificationListener";
Expand Down Expand Up @@ -147,6 +148,17 @@ export class NotificationManager implements INotificationManager {
}
};

/**
* Notification for events being retried.
* @param events - The array of events that are being retried.
* @param statusCode - The HTTP status code that triggered the retry.
*/
_self.eventsRetry = (events: ITelemetryItem[], statusCode: number): void => {
_runListeners(_listeners, STR_EVENTS_RETRY, _asyncNotifications, (listener) => {
listener.eventsRetry(events, statusCode);
});
};

_self.offlineEventsStored = (events: ITelemetryItem[]): void => {
if (events && events.length) {
_runListeners(_listeners, STR_OFFLINE_STORE, _asyncNotifications, (listener) => {
Expand Down Expand Up @@ -254,6 +266,15 @@ export class NotificationManager implements INotificationManager {
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
}

/**
* Notification for events being retried.
* @param events - The array of events that are being retried.
* @param statusCode - The HTTP status code that triggered the retry.
*/
eventsRetry?(events: ITelemetryItem[], statusCode: number): void {
// @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging
}

/**
* [Optional] This event is sent if you have enabled perf events, they are primarily used to track internal performance testing and debugging
* the event can be displayed via the debug plugin extension.
Expand Down
Loading
Loading