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

Multiple IA Instances or collection of subscriptions on client side #1595

Open
davallen opened this issue Jun 29, 2021 · 17 comments
Open

Multiple IA Instances or collection of subscriptions on client side #1595

davallen opened this issue Jun 29, 2021 · 17 comments
Assignees

Comments

@davallen
Copy link

We are leveraging the ApplicationInsights-JS package in a micro UI architecture where many teams are contributing to overall user experience. These teams would like to be able to monitor and instrument with their own instance of App Insights subscription.

Would like to be able to instantiate or maintain a collection of app insights subscriptions on client side and be able to push data to any of them based on configuration.

We have tried to implement all telemetry to a single app insights instance, but storage threshold is becoming an issue.

Is there any way to be able to selectively push telemetry data to multiple subscriptions in the same application.

@MSNev
Copy link
Collaborator

MSNev commented Jun 29, 2021

Yes you can, as long as all of the subscription(s) all end up in the same region (and therefore get sent to the same location -- without the server sending service side redirects).

Then there are 2 simple methods

  • Before you call appInsights.track[XXX}() calls, simply set the "iKey" property on the event to the teams event (evt.iKey = "xxx")
  • You can also add a telemetryInitializer which does the same, although at this level the default iKey will already have been assigned to the instrumentationKey provided during initialization. Soooo, you could "replace" the telemetryItem.iKey value with the value you want (probably based on some other value that you temporarily assign to the telemetry item -- sort of like setting the iKey above)

What this doesn't do for you is that all of the automatic events (PageView, Instrumented XHR or fetch) requests etc will still use be assigned the default instrumentationKey and these will only get sent once (which is fine -- most of the time, except for maybe the Page view for different subscriptions). For the ajax (XHR, fetch) dependency calls this is where the telemetryInitializer option would work better as you can switch the iKey based on the URL.

Another option
You can create "multiple" appInsights instances and use them directly, this is easiest when consuming via the NPM so that all teams could use the same code and just create multiple instances, but it can also be done via the snippet loader you would need multiple "snippets" with each one defining it's own configuration and each with their own INSTRUMENTATION_KEY AND also specifying the name config value which will assign the global instance (on window) to this name and then each team would have their own instances.

So the following will create 2 global objects
window.team1AppInsights and window.team2AppInsights

<script type="text/javascript">
!function(T,l,y){<!-- Removed the Snippet code for brevity -->}(window,document,{
src: "https://js.monitor.azure.com/scripts/b/ai.2.min.js",
crossOrigin: "anonymous",
onInit: function (sdk) {
  sdk.addTelemetryInitializer(function (envelope) {
    envelope.data.someField = 'This item passed through my telemetry initializer';
  });
}, // Once the application insights instance has loaded and initialized this method will be called
 name: "team1AppInsights",
cfg: { // Application Insights Configuration
    instrumentationKey: "YOUR_INSTRUMENTATION_KEY - TEAM1"
}});
</script>
<script type="text/javascript">
!function(T,l,y){<!-- Removed the Snippet code for brevity -->}(window,document,{
src: "https://js.monitor.azure.com/scripts/b/ai.2.min.js",
crossOrigin: "anonymous",
onInit: function (sdk) {
  sdk.addTelemetryInitializer(function (envelope) {
    envelope.data.someField = 'This item passed through my telemetry initializer';
  });
}, // Once the application insights instance has loaded and initialized this method will be called
name: "team2AppInsights",
cfg: { // Application Insights Configuration
    instrumentationKey: "YOUR_INSTRUMENTATION_KEY - TEAM2"
}});
</script>

Note: I've not tried this recently, so if you encounter an issue with this let us know as that would be considered a bug.

@MSNev
Copy link
Collaborator

MSNev commented Jul 1, 2021

Just fixed up the "location" of the name config in the above as it should be at the same level as cfg (doh!) just as is documented in the main readme https://github.com/Microsoft/ApplicationInsights-JS#snippet-setup-ignore-if-using-npm-setup

@FredUK
Copy link

FredUK commented Aug 18, 2021

@MSNev I've been trying to achieve the same thing and took your suggestion and other various approaches but with no success:

1) Multiple instances

My first attempt was having 2 global instances as you mentioned and call them separately but this didn't work as expected and only logged it to one of the instances. Apparently the last one that is defined in the HTML is the only one that works as I switched them around and now the instance that was not getting logged, was getting logged, and the other didn't log anymore.

I've also tried refactoring the 2 global objects as not to need to have to duplicate the whole js snippet by converting it to NOT be IIFE ( Immediately invoked function expression ) and instead assign it to a variable so I could call it multiple times with different config objects:

    const aiConfig1 = {
      name: 'appInsights',
      src: "https://js.monitor.azure.com/scripts/b/ai.2.min.js", crossOrigin: "",
      cfg: {instrumentationKey: instrumentationKey, disableAjaxTracking: true}
    };
    const aiConfig2 = {
      name: 'appInsightsSecondary',
      src: "https://js.monitor.azure.com/scripts/b/ai.2.min.js", crossOrigin: "",
      cfg: {instrumentationKey: instrumentationKeySec, disableAjaxTracking: true}
    };

    const appInsightsInit= function(T,l,y){<!-- Removed the Snippet code for brevity -->} ):a()};

    appInsightsInit(window, document, aiConfig1);
    appInsightsInit(window, document, aiConfig2);

This had no difference from the 2 separate global objects and only the last one was getting logged.

2) iKey as an event property

My second attempt was specifing the iKey or instrumentationKey on the event itself but that didn't work either:

appInsights.trackEvent({name: 'myEvent', iKey: alternativeKey})

I tried using different names such as iKey, instrumentationKey, and specifying them in different places: inside properties: {}, inside cfg{}, inside config{} , etc.. nothing worked.

3) Overriding appInsights.config.instrumentationKey

Then my last idea was to override the appInsights.config.instrumentationKey after logging an event, and then log that event a second time, hopefully using the new key.

    appInsights.trackEvent({name: event, properties: data});

    if (secondaryInstrumentationKey) {

      // Save primary InstrumentationKey
      const originalKey = appInsights.config.instrumentationKey;

      // Temporarly override instrumentationKey and log the event again
      appInsights.config.instrumentationKey = secondaryInstrumentationKey;
      appInsights.trackEvent({name: event, properties: data});

      // Restore the original instrumentationKey
      appInsights.config.instrumentationKey = originalKey;
    }

This worked but it seems very inconsistent. Sometimes the event doesn't get logged to the second instance probably due to how the SDK debounces and aggregates as many events as possible before POSTing them to App Insights. I really have no idea..

I think the simplest thing would be to have the ability to override the iKey when calling appInsights.trackEvent().

@Karlie-777
Copy link
Contributor

Hi @FredUK
You are correct, name in the config replace the same global appInsightsSDK and therefore only the core of the last one is initialized!
We are investigating on this and working on a solution.

@Karlie-777
Copy link
Contributor

Hi @FredUK
After discussion with @MSNev ,
currently the best solution should be loading your second appInsights after initialization of the first one.
You can add onInit callback function in your first appInsights configuration, the onInit will be called once appInsights initialization is done.
so, your code might look like this:

<script>

      const secondScript = () => !function(T,l,y){ var S=T.location,k="script" ...   
            src: "https://js.monitor.azure.com/scripts/b/ai.2.min.js",
            crossOrigin: "anonymous", 
            name:"appInsights2",
            cfg: {
                instrumentationKey: "appInsights2"
      }});

      !function(T,l,y){var S=T.location,k="script" ...
      src: "https://js.monitor.azure.com/scripts/b/ai.2.min.js", 
      crossOrigin: "anonymous", 
      onInit: secondScript, 
      name:"appInsights1",
      cfg: { 
          instrumentationKey: "appInsights1"
      }});
</script>

please let us know if the solution works!

@MSNev
Copy link
Collaborator

MSNev commented Aug 24, 2021

@FredUK, and just to circle back on your options (sorry I was out last week).

  1. @Karlie-777 found a race condition in the way the scripts are loaded and initializing around the use of a "global" instance name (not a simple fix) -- So her response above effectively works around this by serializing the initialization and avoiding the race. And if both are using the same script source, then the browser should re-use the previously (from first script) downloaded code and the 2nd should get initialized almost straight away.

  2. This works (but) only for direct usages of the track method (as this is a supported scenario for track). @Karlie-777 can you please raise another specific issue that trackEvent() doesn't allow replacing the iKey (we will need to add this as an optional value for the IEventTelemetry```` (the same as it is already for ITelemetryItem```) and set it if present after creating the envelope -- if present)

    I think the simplest thing would be to have the ability to override the iKey when calling appInsights.trackEvent().

    100% Agree as called out above in # 2 response

  3. This is not a supported scenario and it's inconsistent because we don't (currently) support "changing" the config after initialization (some components cache the config -- which is why it's inconsistent). There would also be possible race conditions between changing, "track" events and automatic events that could use the wrong iKey (even if you restored directly after the track call -- primarily if sending a synchronous event or during the "unload" cycle (which is all treated as synchronous))

@FredUK
Copy link

FredUK commented Aug 26, 2021

Hi @Karlie-777 and @MSNev , thanks for both detailed replies. That really helps understanding why it wasn't working.
I have attemped your suggestion of initiating the second appInsights on the onInit of the first one but it does not get initiated.

I have created a small jsfiddle sample here if you want to take a look.

If you click the "trackEvent" button and look at the console you will see it will log the first placeholder iKey but for the second log, it says appInsights2 is not defined. It seems the second global one is not initialized at all, even when on initiated on onInit.

Just wondering if the suggestion had been tested in the past or if I'm doing something wrong?

Thanks.

@MSNev
Copy link
Collaborator

MSNev commented Aug 26, 2021

Hi @FredUK,

The OnInit, needs to be outside of the cfg: {} as it's a snippet config value and not the AI config.

I changed your jsFiddle for the 1st instance like this

    name: 'appInsights1',
    src: "https://js.monitor.azure.com/scripts/b/ai.2.min.js",
    crossOrigin: "",
    onInit: () => {
        console.log("On Init called!");
        secondScript();
    },
    cfg: {
        instrumentationKey: instrumentationKey,
        disableAjaxTracking: true
    }});

@FredUK
Copy link

FredUK commented Aug 27, 2021

Thanks @MSNev 🏅 , that worked. I guess I spent too much time messing with it that I messed up where I put the onInit. :)

@FredUK
Copy link

FredUK commented Aug 27, 2021

Using this approach I' am sometimes getting a CORB error. I heard of CORS before but not CORB. Unfortunately I cannot figure out why I only get this warning sometimes and not all the time. I just thought I should mention in case it helps:

image

@MSNev
Copy link
Collaborator

MSNev commented Aug 28, 2021

Ok, a CORB error is a little tricky to diagnose as it's going to depend on what the server returned in the body of the response and the Content-Type header.

As such I think the easiest way to track this down (if you can get a consistent repro) would be a fiddler trace with the offending response(s) to https://dc.services.visualstudio.com/vs/track, so this may contain sensitive data attaching to this thread is not advised. So if you can get a trace with this captured let me know (here) and we can find some other way to share the file.

@FredUK
Copy link

FredUK commented Aug 31, 2021

Hi again, I have managed to capture it in Fiddler just not sure how I will be able to send you the google drive link as Github has no private messaging.

As I mentioned this only seems to happen sometimes. Looking at the server response for a working logged event and one that threw a CORB message the only difference I can see is that in the response body for the CORB one there is no appId property returned from the server.

This response was fine

{
    "itemsReceived": 1,
    "itemsAccepted": 1,
    "errors": [],
    "appId": "c9e8cc9d-351d-42b3[...]" // shortened for security reasons
}

This response threw a CORB error

{
    "itemsReceived": 1,
    "itemsAccepted": 1,
    "errors": []
}

Hope this helps. Let me know if you are still interested in the fiddler session file and how I can make the link get to you.

@MSNev
Copy link
Collaborator

MSNev commented Aug 31, 2021

@FredUK - excellent, I suspect it's something else in the response. I'm currently setting up an email alias that will enable you to send the trace (or a link) directly to me. It normally takes a few hours to get approved / processed will ping again once it is complete and active. It will be an @microsoft.com email address.

@FredUK
Copy link

FredUK commented Sep 1, 2021

Thanks @MSNev , email has been sent. You can remove it from comment above if you'd like.

@MSNev
Copy link
Collaborator

MSNev commented Sep 1, 2021

Thanks @FredUK, I'm reviewing the trace and while the response seems fine there are differences in the outbound request specifically the Content-Type which might be causing the issue -- investigating.

@MSNev
Copy link
Collaborator

MSNev commented Sep 1, 2021

I've split out the CORB issue to #1653, as the requests that are returning the CORB issue appear to be originating from the _beaconSender() (using the sendBeacon API). Assuming this is the only case this is a non-blocking issue as the JS code doesn't need the response from a sendBeacon request (because the API doesn't return the response).

Looking at the trace provided by @FredUK the events where still ingested by the service, so it does not appear that this is blocking events from being ingested

@FredUK
Copy link

FredUK commented Sep 2, 2021

Thanks @MSNev , great stuff. Appreciate your support and the detailed information. I will eventually wait for #1642 and the ability to specify the iKey on tracKEvent() instead of having to load 2 application insights instances.

@Karlie-777 Karlie-777 linked a pull request Sep 7, 2021 that will close this issue
@MSNev MSNev removed a link to a pull request Sep 7, 2021
@MSNev MSNev removed their assignment Jan 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants