APM tips blog

Blog about application monitoring.

Open Source Signing

| Comments

We enabled open source signing for Application Insights SDK on github. Open source signing allows you to build assembly that matches identity of those officially built by Microsoft.

When you import Application Insights NuGet package reference like this will be added to your project. Note that this reference is added to the assembly with the specific public key token:

1
2
3
4
<Reference Include="Microsoft.ApplicationInsights, PublicKeyToken=31bf3856ad364e35, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
  <HintPath>..\packages\Microsoft.ApplicationInsights.2.0.0.0\lib\net45\Microsoft.ApplicationInsights.dll</HintPath>
  <Private>True</Private>
</Reference>

You may also have some already compiled assemblies that has a reference to Application Insights SDK. Those references would also be on strongly named assembly.

Open source signing allows you to change the code of Application Insights SDK, compile it and replace original assembly for testing. Applicaiton Insights assembly you’ll compile locally will have the same public key token as one compiled by Microsoft. Here is what sn tool will output:

1
2
3
4
5
6
7
8
9
10
11
12
13
>sn -Tp Microsoft.ApplicationInsights.dll

Microsoft (R) .NET Framework Strong Name Utility  Version 4.0.30319.18020
Copyright (c) Microsoft Corporation.  All rights reserved.

Public key (hash algorithm: sha1):
0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f6
7871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae
0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454
307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652b
f5c308055da9

Public key token is 31bf3856ad364e35

So you don’t need to change public key token in project file and you don’t need to recompile other assemblies referring Application Insights SDK. You can just replace a file and test your changes.

There are some limitations with open source signing. First, strong name verification will obviously fail:

1
2
3
4
5
6
>sn -vf Microsoft.ApplicationInsights.dll

Microsoft (R) .NET Framework Strong Name Utility  Version 4.0.30319.18020
Copyright (c) Microsoft Corporation.  All rights reserved.

Failed to verify assembly -- Strong name validation failed.

So you may need to disable verification for this public key token (don’t forget to run this command for the correct bittness - x86 or x64):

1
>sn -Vr Microsoft.ApplicationInsights.dll

Next limitation is related to the behavior of ASP.NET infrastracture. Even if you turned strong name verification off on computer, your ASP.NET applicaitons will likely fail with the message like this:

1
2
3
4
5
6
7
A first chance exception of type 'System.IO.FileLoadException' occurred in mscorlib.dll

Additional information: Could not load file or assembly 'Microsoft.ApplicationInsights.dll,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its
dependencies. Strong name signature could not be verified.  The assembly may have
been tampered with, or it was delay signed but not fully signed with the correct
private key. (Exception from HRESULT: 0x80131045)

The issue explained in details at IIS forum.

When using .NET 4, shadow copying assemblies in an application for which assemblies rarely ever change has improved. In previous versions of ASP.NET, there was often a noticeable delay in application startup time while assemblies were being shadow copied. Now, the framework checks the file date/time of an application’s assemblies and compares that with the file date/time of any shadow copied assemblies. If they are the same, the shadow copying process does not occur. This causes the shadow copying process to kick off only if an assembly has been physically modified.

The process would look something like this for each assembly:
1. Copy assembly from application location to temporary location
2. Open assembly
3. Verify assembly name
4. Validate strong name
5. Compare update to current cached assembly
6. Copy to shadow copy location (if newer)
7. Remove assembly from temporary location

Shadow copying is important if you are modifying assemblies directly in a live application.

But if you want to skip strong name assembly, you must disable shadow copying.

So you need to disable shadow copying for your ASP.NET application in web.config:

1
2
3
<system.web>
  <hostingEnvironment shadowCopyBinAssemblies="false" />
</system.web>

More information on open source signing can be found at corefx documentaiton page.

With open source signing it is much easier to validate changes you may need in Application Insights SDK. We always happy to hear your feedback and accept your contribution!

Nuget Error: An Entry With the Same Key Already Exists

| Comments

UPDATE: Issue got resolved. It turns out that tha list of dependencies is merged by the server and there are some NuGet servers out wild which will merge dependencies for all platforms. Cleaning cache at %userprofile%\.nugget and %localappdata%\nugget and not using those servers solves the problem. Hurrah Open source for resolving issues quickly!

Original post:

Sometimes installing the NuGet you can see the error message An entry with the same key already exists:

1
2
3
4
5
6
7
8
9
PM> Install-Package "Microsoft.ApplicationInsights.DependencyCallstacks" -Source "https://www.myget.org/F/applicationinsights-sdk-labs/" -Pre
Attempting to gather dependencies information for package 'Microsoft.ApplicationInsights.DependencyCallstacks.0.20.0-build14383' with respect to project 'WebApplication3', targeting '.NETFramework,Version=v4.5.2'
Attempting to resolve dependencies for package 'Microsoft.ApplicationInsights.DependencyCallstacks.0.20.0-build14383' with DependencyBehavior 'Lowest'
Install-Package : An entry with the same key already exists.
At line:1 char:1
+ Install-Package "Microsoft.ApplicationInsights.DependencyCallstacks"  ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Install-Package], Exception
    + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PackageManagement.PowerShellCmdlets.InstallPackageCommand

Changine the order of installing NuGets sometimes helped so I never tried to troubleshoot it further. Also I found this forum post so I figured it maybe some generic problem.

Yesterday I got this error again. It took me 10 minutes to troubleshoot it. Simple steps:

  • Open windbg (32-bit) and attach to devenv.exe
  • Make it stop on managed exceptions: sxe clr
  • Load sos using command loadby sos clr

Then I printed stack:

1
2
3
4
5
6
7
8
9
0:061> !clrstack
OS Thread Id: 0x3da8 (61)
Child SP       IP Call Site
29abc3e0 76ca3e28 [HelperMethodFrame: 29abc3e0] 
29abc490 727b7d91 System.ThrowHelper.ThrowArgumentException(System.ExceptionResource) [f:\dd\NDP\fx\src\compmod\system\collections\generic\throwhelper.cs @ 63]
29abc4a0 729ef49a System.Collections.Generic.TreeSet`1[[System.Collections.Generic.KeyValuePair`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]], mscorlib]].AddIfNotPresent(System.Collections.Generic.KeyValuePair`2) [f:\dd\NDP\fx\src\compmod\system\collections\generic\sorteddictionary.cs @ 803]
29abc4b0 723b6149 System.Collections.Generic.SortedDictionary`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].Add(System.__Canon, System.__Canon) [f:\dd\NDP\fx\src\compmod\system\collections\generic\sorteddictionary.cs @ 167]
29abc4c4 17277f3b NuGet.Resolver.ResolverPackage..ctor(System.String, NuGet.Versioning.NuGetVersion, System.Collections.Generic.IEnumerable`1, Boolean, Boolean)
29abc578 069d15a4 NuGet.Resolver.PackageResolver.Resolve(NuGet.Resolver.PackageResolverContext, System.Threading.CancellationToken)

It’s easy to look at sources as NuGet is open source. Here is line of code that fails. We pass list of dependencies to the constructor of ResolverPackage:

1
resolverPackages.Add(new ResolverPackage(package.Id, package.Version, dependencies, package.Listed, false));

and it in turns add all dependencies into collection:

1
_dependencyIds.Add(dependency.Id, dependency.VersionRange == null ? VersionRange.All : dependency.VersionRange);

So I took all objects on the stack using !dso and found the array of dependencies there. You can see that is consist of 22 dependencies and two of them: 249c9338 and 249c93b8 have the same name:

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
0:061> !DumpArray /d 249ca23c
Name:        NuGet.Packaging.Core.PackageDependency[]
MethodTable: 1ecba294
EEClass:     72ce3750
Size:        100(0x64) bytes
Array:       Rank 1, Number of elements 22, Type CLASS
Element Methodtable: 1ecb9d24
[0] 249c9338
[1] 249c9378
[2] 249c93b8
[3] 249c9448
[4] 249c94d8
[5] 249c9568
[6] 249c9604
[7] 249c9694
[8] 249c9724
[9] 249c97b4
[10] 249c9844
[11] 249c98d4
[12] 249c9964
[13] 1d2e5a70
[14] 1d2e5b00
[15] 1d2e5b90
[16] 1d2e5c20
[17] 1d2e5cb0
[18] 1d2e5d40
[19] 1d2e5dd0
[20] 249ca19c
[21] 249ca22c

0:061> !DumpObj /d 249c9338
Name:        NuGet.Packaging.Core.PackageDependency
MethodTable: 1ecb9d24
EEClass:     1ec961e8
Size:        16(0x10) bytes
File:        C:\PROGRAM FILES (X86)\MICROSOFT VISUAL STUDIO 14.0\COMMON7\IDE\EXTENSIONS\Q04IPECQ.ZWQ\NuGet.Packaging.Core.Types.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
73101d7c  4000008        4        System.String  0 instance 1d2dc7f4 _id
11b5bd88  4000009        8 ...ning.VersionRange  0 instance 1d2fdfa8 _versionRange

0:061> !DumpObj /d 1d2dc7f4
Name:        System.String
MethodTable: 73101d7c
EEClass:     72ce3620
Size:        52(0x34) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      Microsoft.Bcl.Async
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
73103cc4  4000243        4         System.Int32  1 instance       19 m_stringLength
731027c0  4000244        8          System.Char  1 instance       4d m_firstChar
73101d7c  4000248       40        System.String  0   shared   static Empty
    >> Domain:Value  0112e5a8:NotInit  <<
      
0:061> !DumpObj /d 249c93b8
Name:        NuGet.Packaging.Core.PackageDependency
MethodTable: 1ecb9d24
EEClass:     1ec961e8
Size:        16(0x10) bytes
File:        C:\PROGRAM FILES (X86)\MICROSOFT VISUAL STUDIO 14.0\COMMON7\IDE\EXTENSIONS\Q04IPECQ.ZWQ\NuGet.Packaging.Core.Types.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
73101d7c  4000008        4        System.String  0 instance 1d2dc8d0 _id
11b5bd88  4000009        8 ...ning.VersionRange  0 instance 1d2fdfe0 _versionRange

0:061> !DumpObj /d 1d2dc8d0
Name:        System.String
MethodTable: 73101d7c
EEClass:     72ce3620
Size:        52(0x34) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      Microsoft.Bcl.Async
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
73103cc4  4000243        4         System.Int32  1 instance       19 m_stringLength
731027c0  4000244        8          System.Char  1 instance       4d m_firstChar
73101d7c  4000248       40        System.String  0   shared   static Empty
    >> Domain:Value  0112e5a8:NotInit  <<

Looking at nuspec you can see that Microsoft.Bcl.Async is a dependency for both platforms - Framework 4.0 and Windows 8:

1
2
3
4
5
6
7
8
<group targetFramework=".NETFramework4.0">
<dependency id="Microsoft.Bcl.Async" version="1.0.168" />
<dependency id="Microsoft.Diagnostics.Tracing.EventSource.Redist" version="1.1.24" />
</group>
<group targetFramework=".NETFramework4.5" />
<group targetFramework="WindowsPhone8.0">
<dependency id="Microsoft.Bcl.Async" version="1.0.168" />
</group>

So it seems that NuGet do not distinguish dependencies for the different plaforms while building the list of references. So I filed the issue at GitHub and hope it will be resolved soon.

Workaround is simple if the list od dependencies for the platform is small. Just add -DependencyVersion Ignore when calling Install-Package. You’ll need to install all dependencies manually.

When everything is open source it is very easy to troubleshoot issues. So we open sourcing more code of Application Insights SDK. Now it is server telemetry channel. See this PR.

Operational Insights Agent for SCOM

| Comments

Quite unusual tip today. You may heard of System Center Operations Manager and the fact it provides APM capabilities. Where APM stands for Application Performance Monitoring. You may also heard of Operational Insights which can work as an attach service for Operations Manager.

What you may not know that the latest agent distributed for Operational Insights is not only compatible with System Center, but also brings more APM capabilities than regular System Center SCOM 2012 R2 agent has. It has number of bugfixes, perf improvements and couple features support like monitoring of MVC 5 applicaitons.

You can download this agent here:
- 64-bit windows - 32-bit windows

Another interesting fact about this agent is that looking into the folder %programfiles%\Microsoft Monitoring Agent\Agent\APMDOTNETAgent\V7.2.10375.0\x64 you will find the files I mentioned in the post explaining how Application Insights tracks dependencies. So installing this agent and enabling APM will help Application Insights SDK collect reacher informaiton about your applicaiton http and SQL dependencies so you don’t need to install Status Monitor.

More Telemetry Channels

| Comments

Application Insights SDK works on many platforms. You can use it to send telemetry Desktop applicaitons, web services, phone apps. All these platforms has their specifics. Phone and desktop apps typically has as single user and small number of events from every particular instance of applicaiton. Services serves many users the same time and have very high load. Devices can go online and offline very often. Services are typically always online.

Initial versions of Applicaiton Insights SDK attempted to have a single channel that will account for all these differences. However we found that it is not easy to accomodate them in a single channel. So now we have three different channel implementations:

  • InMemoryChannel
  • Persistence channel for devices
  • Windows Server telemetry channel

Sampling that I mentioned in this post mostly applies to the windows server telemetry channel. So I’ve updated that post.

InMemoryChannel

InMemory channel Microsoft.ApplicationInsights.Channel.InMemoryChannel is a lightweight loosy telemetry channel that is used by default. It will batch data and initiate sending every SendingInterval (default is 30 seconds) or when number of items exceeded MaxTelemetryBufferCapacity (default is 500. It also will not retry if it failed to send telemetry.

Package: this channel is part of Applicaiton Insights API package. Sources: InMemoryChannel.cs on github.

Persistence channel for devices

Persistence channel for devices Microsoft.ApplicationInsights.Channel.PersistenceChannel is a channel optimized for devices and mobile apps and works great in offline scenarios. It requires a file storage to persist the data and you should use this constructor to specify folder you want to use for storage.

I already explained how this channel will work for unhandled exceptions. It writes events to disk before attempting to send it. Next time app starts - event will be picked up and send to the cloud. Furthermore, if you are running multiple instances of an applicaiton - all of them will write to the disk, but only one will be sending data to http endpoint. It is controled via global Mutex.

Package: Application Insights Persisted HTTP channel Sources: PersistenceChannel.cs on github

Windows Server telemetry channel

Windows Server telemetry channel Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.ServerTelemetryChannel is a channel optimized for high volume data delivery. It will send data to endpoint first and only attempt to persist data if endpoint cannot be reached. It is using current user’s local app data folder (%localAppData%\Microsoft\ApplicationInsights) or temp folder (%TMP%).

This channel implements exponential retry intervals and respect Retry-After header set by the server. It solves the problem we call “spiral of death”. Spiral of death happens when endpoint was temporary unavbailable or you hit the throttling limit. Channel starts persisting data if it cannot send it. After some time your throttling limit will be cleared up or connectivity issue will be fixed. So channel will start sending all the new and persisted data. With the big load you may hit throttling limit very easily again. So data will be rejected again. And you’ll start persiting it entering the spiral.

Package: Windows Server telemetry channel Sources: Not public yet.

Summary

Different applications requires different channels. NuGet packages will configure proper channel for you. However if you configure Application Insights manually you need to know which channel is right for you.

Web SDK 1.2.1 Structure Changes

| Comments

This blog post was written by Anastasia Baranchenkova

In version 1.2.1 there were 2 major changes in the structure.

  1. All Web SDK assemblies were renamed:

    • Microsoft.ApplicationInsights.Web.dll was renamed on Microsoft.AI.Web.dll
    • Microsoft.ApplicationInsights.Extensibility.Web.TelemetryChannel.dll was renamed on Microsoft.AI.ServerTelemetryChannel.dll
    • Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.dll was renamed on Microsoft.AI.PerfCounterCollector
    • Microsoft.ApplicationInsights.Extensibility.DependencyCollector.dll was renamed on Microsoft.AI.DependencyCollector.dll
  2. Logic that does not depend on web was moved out from Microsoft.ApplicationInsights.Extensibility.Web to Microsoft.AI.WindowsServer.

Microsoft.AI.WindowsServer.dll is distributed with the new nuget package Application Insights Windows Server. This nuget package can be installed on Worker roles projects, windows services or console applications.

The following telemetry initailizers are part of this assembly (all of them were part of web sdk assembly before):

  • DeviceTelemetryInitializer. This telemetry initailizer fills most of device context properties.
  • DomainNameRoleInstanceTelemetryInitializer. This telemetry initializer sets device context RoleInstance to machine FQDN name.
  • BuildInfoConfigComponentVersionTelemetryInitializer. This telemetry initializer sets component context Version property using buildinfo.config if you have msbuild integration.
  • AzureRoleEnvironmentTelemetryInitializer. This telemetry initailizer sets RoleInstance and RoleInstanceName in case if your application is web or worker role.

The following telemetry modules are part of this assembly:

  • DeveloperModeWithDebuggerAttachedTelemetryModule. This module enables VS F5 experience.
  • UnhandledExceptionTelemetryModule. This new module tracks unhandled exceptions in case if your application is a worker role, windows service or console application.
  • UnobservedExceptionTelemetryModule. This new module tracks task unobserved exceptions.

Additionally this new nuget package changes ApplicationInsights.config file properties so it is copied to the output. Without that in the previous SDK version worker role monitoring did not start till you manually did the same.

Disable Browser Telemetry From the Page

| Comments

There is a feedback page for the Azure portal. One of the feedback item is “I want to block certain items it sent to the app insights like the pagename. Can this be done?”. One of my previous posts explains how Application Insights javascript snippet works. Now I’ll try to answer the question and discuss another scenario - how to disable reporting of telemetry under certain conditions. I hope this post will help to understand the snippet even better.

Do not send page name to the portal

So - can page name be hidden from the Application Insights. Yes, sure. The standard JavaScript snippet ends with these two lines:

1
2
window.appInsights = appInsights;
appInsights.trackPageView();

So PageView event is not begin sent automatically. It is sent by this call to trackPageView you’ve pasted to your page. Looking at signature of this function you can find out that it accept four parameters, two of which are name and url:

1
public trackPageView(name?: string, url?: string, properties?: Object, measurements?: Object)

When you call trackPageView without parameters it will use window.document.title and window.location.href correspondingly as you can see on github.

If for some reasons you don’t want to use window.document.title as a page name you can replace it to window.location.pathname or any custom string you like:

1
2
window.appInsights = appInsights;
appInsights.trackPageView(window.location.pathname, window.location.href);

Disable telemetry from the page

Now, let’s imagine that you don’t want to report telemetry from certain pages or under certain conditions.

Do not inject snippet

Easiest way to disable any telemetry from the page is not to inject the javascript snippet. In many applications all pages are built out of one template. So you will need to have some server-side logic to disable snippet injection for the certain pages.

Typically for ASP.NET applications you will inject Application Insights javascript snippet to the template file Views\Shared\_Layout.cshtml. If you are using razor you can have condition code like this:

1
2
3
4
5
@if (needTelemetry) {
  <script language="javascript">
      Application Insights snippet code...
  </script>
}

Do not initialize javascript

There are cases when you do not run server-side code to generate snippets or you want to decide whether to send telemetry on client side. For instance, if you have a static HTML page and you want to disable tlemetry when it is opened as a file from file system - you can have client side “big switch” implemented like this:

1
2
3
4
5
<script language="javascript">
  if (location.protocol != "file:") {
      Application Insights snippet code...
  }
</script>

The big downside of this approach is that all custom telemetry calls like the call to appInsights.trackEvent will now fail. To fix this issue - you can create your own mock object that will replace the real appInisghts. Here is how this mock object may look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script language="javascript">
  if (location.protocol != "file:") {
      Application Insights snippet code...
  }
  else {
      var appInsights = window.appInsights || function (config) {
          var t = {};
          function s(config) {
              t[config] = function () {
                  console.log(config + " was called with the arguments: " +
                      Array.prototype.slice.call(arguments).toString());
              }
          }
          for (i = ["Event", "Exception", "Metric", "PageView", "Trace"]; i.length;)
              s("track" + i.pop());
          return t;
      }();
      window.appInsights = appInsights;
  }

Disable page view reporting

In some cases you may want to disable page view reporting. For instance, you may not want to have page view statistics from the test environment or development machine. Just wrap the call to trackPageView into condition like this:

1
2
3
if (!document.location.href.startsWith("http://localhost")) {
  appInsights.trackPageView();
}

Disable error reporting

It is also easy to disable reporting of javascript errors from the development machine. Set disableExceptionTracking setting in appInsights configuration:

1
2
3
4
5
6
var appInsights = window.appInsights || function (config) {
      //snippet code...
}({
  instrumentationKey: "595f59fc-f85f-425f-a336-827b13c1a03c",
  disableExceptionTracking: document.location.href.startsWith("http://localhost")
});

With Application Insights you are in full control over telemetry your application sends to the portal. We release right from github so you can always review what data Application Insights SDK collects.

Application Insights for Desktop Applications

| Comments

Good news, we open sourced Application Insights SDK for .NET. For now, just shared part of it - devices and ASP.NET SDKs are coming soon. Internally we call it “Core SDK”. In this article I want to describe how to use this SDK to track usage and crashes of your desktop application.

Let’s say you have a simple WPF app that shows the temperature in the location you’ve specified using zip code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public MainWindow()
{
  InitializeComponent();
}

private async void buttonClick_requestCurrentTemperature(object sender, RoutedEventArgs e)
{
  // get zip code from the text box
  var zip = zipTextBox.Text;
  
  HttpClient client = new HttpClient();
  var apiUrl = string.Format(
        "http://api.openweathermap.org/data/2.5/weather?units=metric&zip={0},us",
        zip);
  var result = await client.GetStringAsync(apiUrl);
  
  JObject o = JObject.Load(new JsonTextReader(new StringReader(result)));
  var temperature = o.SelectToken("main.temp").Value<float>();
  
  // display the temperature:
  label.Content = temperature.ToString();
}

From usage perspective you might be curious what zip code being used most often. It is easy to track with Application Insights. You’ll need to install Application Insights API NuGet. Than you initialize Application Insights. Since I only have one form in my applicaiton, I’ve created a private telemetry client that will be used in this WPF form:

1
2
3
4
5
6
7
8
9
10
private readonly TelemetryClient telemetryClient;

public MainWindow()
{
  TelemetryConfiguration config = TelemetryConfiguration.CreateDefault();
  config.InstrumentationKey = "Foo";
  telemetryClient = new TelemetryClient(config);
  
  InitializeComponent();
}

With this you can now track telemetry event every time somebody click the button:

1
2
telemetryClient.TrackEvent("TemperatureRequested",
  new Dictionary<string, string>() { { "zip", zip } });

In the Azure portal you can open Metric Exporer and group events by zip code:

Once configured you’ll see a view like this that can be saved as favourite view for later access:

It was easy. Now let’s say you want to decide whether you need to show a spinner to your customer while she is waiting for results. You wonder - how long does it typically take to display the temperature.

Every event has a metrics collection you can associate with it. Just start a timer and report the duration as a metric. Now you can have charts showing the average duration for TemperatureRequested event. Code may look like this:

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
private async void buttonClick_requestCurrentTemperature(object sender, RoutedEventArgs e)
{
    var timer = Stopwatch.StartNew();
    var zip = zipTextBox.Text;

    try
    {
        HttpClient client = new HttpClient();
          var apiUrl = string.Format(
               "http://api.openweathermap.org/data/2.5/weather?units=metric&zip={0},us",
               zip);
        var result = await client.GetStringAsync(apiUrl);

        JObject o = JObject.Load(new JsonTextReader(new StringReader(result)));
        var temperature = o.SelectToken("main.temp").Value<float>();
      
        label.Content = temperature.ToString();
    }
    finally
    {
        telemetryClient.TrackEvent("TemperatureRequested",
            new Dictionary<string, string>() { { "zip", zip } },
            new Dictionary<string, double>() { { "duration", timer.ElapsedMilliseconds } });
    }
}

Finally, you want to know how many users of your application clicking this button. So you need to start tracking users and sessions. In my application I’m creating the new session every time user opens the form. I’m using the user name from environment. You need to make sure that user names are unique enough so you’ll get the real number of users.

1
2
3
4
5
6
7
8
9
10
public MainWindow()
{
    TelemetryConfiguration config = TelemetryConfiguration.CreateDefault();
    config.InstrumentationKey = "954f17ff-47ee-4aa1-a03b-bf0b1a33dbaf";
    telemetryClient = new TelemetryClient(config);
    telemetryClient.Context.User.Id = Environment.UserName;
    telemetryClient.Context.Session.Id = Guid.NewGuid().ToString();

    InitializeComponent();
}

You can find more information on usage tracking on Application Insights documentation page.

Note, in this example I do not initialize singleton TelemetryConfiguration.Active and do not use ApplicationInsights.config configuration file. So in the documentation above you cannot just create TelemetryClient c = new TelemetryClient();. This telemetry client will not be initialized - it will not have instrumentaiton key and telemetry channel configured.

Most of the code above it not specific for desktop applications. One of the difference of desktop applications is that they may be running without internet connection. By default when you create TelemetryConfiguration in-memory channel will be used to communicate with the Application Insights backend. This channel has no persistence of events and will lose data if internet connection is not reliable. You may use this channel if it is not the issue for you. However for more reliable telemetry you may want to use persistence channel. For desktop applicaitons use Microsoft.ApplicationInsights.PersistenceChannel NuGet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public MainWindow()
{
    TelemetryConfiguration config = TelemetryConfiguration.CreateDefault();
    config.InstrumentationKey = "954f17ff-47ee-4aa1-a03b-bf0b1a33dbaf";

    config.TelemetryChannel = new PersistenceChannel();
    config.TelemetryChannel.DeveloperMode = Debugger.IsAttached;

    telemetryClient = new TelemetryClient(config);
    telemetryClient.Context.User.Id = Environment.UserName;
    telemetryClient.Context.Session.Id = Guid.NewGuid().ToString();

    InitializeComponent();
}

Persistence channel is optimized for devices scenario when the number of events produced by application is relatively small and connection is unreliable quite often. This channel will write events to the disk into reliable storage first and then attempt to send it. Here is how it works.

Let’s say you want to monitor unhandled exceptions. You’d subscribe on UnhandledException event and in the corresponding callback you want to make sure that telemetry will be persisted. So you call Flush on telemetry client.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

...

private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    ExceptionTelemetry excTelemetry = new ExceptionTelemetry((Exception)e.ExceptionObject);
    excTelemetry.SeverityLevel = SeverityLevel.Critical;
    excTelemetry.HandledAt = ExceptionHandledAt.Unhandled;

    telemetryClient.TrackException(excTelemetry);

    telemetryClient.Flush();
}

The only thing method Flush will do is to make sure that all telemetry events from the buffer are stored in persistence storage. In my case when I enter incorrect zip code - applicaiton will crash with ArgumentExcetpion and I’ll see new file named 20150810005005_84fb4de977e24c8399618daf2c4eb57d.trn into the folder %LocalAppData%\Microsoft\ApplicationInsights\35eb39bd0bb5855e732748ad369ffacc10de7340.

This file has all events scheduled to be send to the backend compressed with GZIP.

1
2
3
4
5
https://dc.services.visualstudio.com/v2/track
Content-Type:application/x-json-stream
Content-Encoding:gzip

H4sIAAAAAAAEAN2YbW/bNhDH3w/YdzD0tjahR8sWkgCps6JZ4yyL3ebFXASUdLI5S5RKUk7cIN99R9mulYd5SR/UYMkbizqefve/I0/UjcFpBkZgDFkkcpknihwWRcoiqljOj7lk05mSpO+5ieUniesDuJRa1HTCMDFDizpOHNKE/LYAroy2oVjlzTYtr2P2OmZ/bPmBZwamR3pez7S7Tsf0A9NEU/YOlmi6cd3RvjvaeUd779Tda8d0Ko3gxqCMMK5AcJoSGc8/gJAIin4sYhOTeF2nj9ZoVUoQhMV4B39M55R/Xo1LkHrG6pYdU8tzqdfx+0jrdm2vE/a6Vsft22DaNPQ8yzRu20ZMFdVPD6mE8bLQIVYRH+nxdjV8tDZZgDACu73RdQxZAYKqUsA5fCpBKohxRgZU4lCGPqqw4lJUihuBY3n4wELkOE0xqO5+ZoVWqmd6dmzc4t+vv9x8r8RdR1BUT96dvL5t2v7/J3mbqHckcEZ5nEJ8qND+PV9foS1s5mJIf90YGsXCxDhmz8FI0f/pKjGjJeY6I4diWuo0n5ZpWhc7w1DoVBt+oGkJrYhynqtWCC2OlmQiJvyMCnSFerV0roOWzEsRgaHR5Bs0GikazY1AiRKwYqiQEK+HkCuFBaRGYOonqVmuBTuFK4XYulZ+l6jiCeOfsAAUcC2qJBWIFlpKyMJ0+XBKu7XO2b6P+cL/dmtQprq49zmUStC03TorQyxDrI9xPge+75g0dhPohjbtUkAFb9tf4KyXDGfX4C6KpLa6LDKkjF8wHudXr/bCUiHFAG/OL8VqiQ9KITDltbV/EF9eOmSYL+AUrtXdMO4534ZhPTUMXTHoM2HppviiYDLB6ismk3veHxnYBkOuaZaSSKKvlHH049s1QZyaIOviPi+53jTIIM8KfLgYgViwCCQ5lEseDSvr1yVLYxCDXMCrvYPo8vKIySKly0GKGthkbzwT+VVlfxCiSHe1yWSUi5SFW1Hcp4oS+j71Iq9r9R0XzF6/nlv3YSgrBSRBHKAx49Pt1nghaIGJJMfrvescaDqgleT1NK4cvMat5CtoHStMnJ7XpbHTdcHx6rTeV9GOxXJAVTS7mAFvirT7FFKdfw0G4o9qeegN/Zgv0PlxVjQmqv+NqHwEUSmYWg5yLIsHa/qHcfcecteLAKE05RpKL9BN1TaysPrPpWuEyjJfJpb1bTXYVMlZ9vM4yZnIsQnIP0u437J/IOQj7Wkn5AWPNefbPJ83xljvO8OR5nNs8vaKx5tNew3VGJD3b0CjMox0g64pplteiK+XX8qwMcontZUX066tJ7WWF9GvrUe6yc5FcwJTGi2b79ZW/z8LdfOj4SVkm4+RveeSJvgyrtgCVq/B25U0XJ/8mgJ8ZpchZ6WcvdEnzyYTbD+7x2woGyPc0WBqByr99rDFvAt3JkDiubCyq9ivcjH/3pg7zjb3MB9/M2wEcseR5h7kT4Db0W5+Ppy/47MEXlSfJl7W94U8/HsyOYKwnE4mmnCqvzDcfrz92DYkBqXPUCer4IwBXuDEVH/k/AeYVOh5mxYAAA==

Next time you’ll start this application - channel will pick up this file and deliver telemetry to the Application Insights.

All the implementation details of this telemetry configuration you may find on github.

There are many more aspect of desktop applications monitoring. I didn’t explain how you will track page views - one of the main concept of devices monitoring. You may also want to track failures, collect diagnostics logs or track dependencies and performance counters. Maybe next time I can cover these aspects as well.

Application Versioning: Semantic or Automatic?

| Comments

Looking at @pksorensen’s example of OWIN middleware to monitor server request I noticed this line of code:

1
rt.Context.Component.Version = "1.0.0";

It will set application version for telemetry items so you can group telemetry by version and understand in which version of application certain exception happened.

Note: Yes, it is confusing name. SDK refers to application as “Component” when in UI it is called “Application”.

This code snippet uses constant string as an application version. My guess is that this version represent semantic version of API call. If you versioned your REST API using urls like this: https://management.azure.com/subscriptions?api-version=2014-04-01-preview you’d probably need to change your middleware to read version from api-version query string parameter.

Semantic version is good for certain telemetry reports. For instance you can see how much traffic goes to which version of API to decide when to shut down older version. However semantic version is something you need to code explicitly - there is no generic way to version applications and APIs.

So instead of using semantic version - we suggest to use automatically generated version number. For instance, this article suggest to use Assembly version of your application as application version.

You’ll need to use wildcard in Assembly.cs:

1
[assembly: AssemblyVersion("1.0.*")]

and use telemetry initializer to initialize application version:

1
2
telemetry.Context.Component.Version =
    typeof(TestBuildInfo).Assembly.GetName().Version.ToString();

Now all telemetry items will be marked with the version like 1.0.5647.32696, where 5647 and 32696 are some semi-random numbers.

Drawback of this approach is that again, you need to write some code. Furthermore, there is no way to detect which assembly is a “main” assembly of an application. So your telemetry initializer should be application specific.

This brings us to the reason I write this blog post. Visual Studio has a feature that I believe undeservedly is not well known and widely used. It is called build information file or BuildInfo.config.

Turing this feature on is simple. Just add a property to your project file:

1
2
3
<PropertyGroup>
  <GenerateBuildInfoConfigFile>true</GenerateBuildInfoConfigFile>
</PropertyGroup>

This will generate the file bin/$(ProjectName).BuildInfo.config when you compile locally with the commit number and auto-generated build label and BuildInfo.config when you publish your application. For instance, once I enabled continues integration in Visual Studio online this file is placed next to web.config:

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
<?xml version="1.0" encoding="utf-8"?>
<DeploymentEvent xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/VisualStudio/DeploymentEvent/2013/06">
  <ProjectName>TestBuildInfo</ProjectName>
  <SourceControl type="Git">
    <GitSourceControl xmlns="http://schemas.microsoft.com/visualstudio/deploymentevent_git/2013/09">
      <RepositoryUrl>/DefaultCollection/temp/_git/temp</RepositoryUrl>
      <ProjectPath>/TestBuildInfo/TestBuildInfo.csproj</ProjectPath>
      <BuiltSolution>/TestBuildInfo.sln</BuiltSolution>
      <CommitId>fbbaf40f804ad2646d5ce70b545fd9fd257feca8</CommitId>
      <ShortCommitId>fbbaf40</ShortCommitId>
      <CommitDateUTC>Thu, 18 Jun 2015 17:07:56 GMT</CommitDateUTC>
      <CommitComment>initial commit</CommitComment>
      <CommitAuthor>Sergey Kanzhelev</CommitAuthor>
    </GitSourceControl>
  </SourceControl>
  <Build type="TeamBuild">
    <MSBuild>
      <BuildDefinition kind="informative, summary">TestBuildInfoApp_CD</BuildDefinition>
      <BuildLabel kind="label">TestBuildInfoApp_CD_20150618.1</BuildLabel>
      <BuildId kind="id">e6e457a1-debf-4bda-b0c9-61344fd55ae2,vstfs:///Build/Build/37</BuildId>
      <BuildTimestamp kind="informative, summary">Thu, 18 Jun 2015 18:08:19 GMT</BuildTimestamp>
      <Configuration kind="informative">Debug</Configuration>
      <Platform kind="informative">AnyCPU</Platform>
      <BuiltSolution>/TestBuildInfo.sln</BuiltSolution>
    </MSBuild>
  </Build>
</DeploymentEvent>

It is great to have this file published with your application as you will always know which version of source code it was built from.

Application Insights Web SDK will install context initializer called BuildInfoConfigComponentVersionContextInitializer that will read this file and mark telemetry items with the application version equal to BuildLabel from the file above. In this example it will be TestBuildInfoApp_CD_20150618.1. Now, looking at your telemetry you can always find the build that produced this binary.

Here are some additional details on how this feature used to work in the old version of Application Insights. From this article you can find that you can use IncludeServerNameInBuildInfo property to enrich BuildInfo.config even more and how to configure copying of this file next to web.config while developing. Do not forget to .gitignore it though…

Do Not Use Context Initializers

| Comments

I’ve been already writing about telemetry initializers. Using them you can add custom properties to telemetry items reported by your application. For every telemetry item Initialize method of all configured telemetry initializer will be called.

Typical use of telemetry initializer is to set properties like user name, operation ID or timestamp. If you look at ApplicationInsights.config file you’ll find a list of default telemetry initializers used for web applications monitoring.

You’d also discover a set of context initializers defined in the standard ApplicationInsights.config file.

The difference between telemetry initializer and context initializer is that context initializer will only be called once per TelemetryClient instance. So context initializers collects information that is not specific to telemetry item and will be the same for all telemetry items. Examples may be application version, process ID and computer name.

So why you should NOT use context initializer?

Reason 1. You never know when context initializer will be called.

Context initializers will be called no later than the first access to Context property of TelemetryClient object instance will be made. If the only TelemetryClient object is one you constructed - you can control when context initializers will be executed. However if you are using features like requests monitoring, dependencies monitoring or performance counters collection - number of telemetry clients will be created under the hood. So if you add context initializer to TelemetryConfiguration.Active.ContextInitializers collection programmatically you can never guarantee that those TelemetryClient objects were not initialized already.

In fact with the changes in 0.16 SDK some initialization ordering was changed and if you’ll add context initializer programmatically in Global.asax to the TelemetryConfiguration.Active.ContextInitializers collection - it will NOT be run for web requests telemetry module. So requests and exception items will not be stamped with the properties you need.

Reason 2. Once set it cannot be unset.

There are cases when you want to change globally set properties in running application. For instance you may need to change environment name from pre-production to production after VIP swap. You cannot achieve it using context initializers. You can remove context initializer from the collection TelemetryConfiguration.Active.ContextInitializers, but properties set by it will still be attached to telemetry items reported from the already created instances of TelemetryClient.

Reason 3. You can do it with telemetry initializer.

You can always use telemetry initializer instead of context initializer. The only thing you need to do is to cache the property value you need to set so it will not be calculated every time.

So instead of context initializer that will set application version like this:

1
2
3
4
5
6
7
public class AppVersionContextInitializer : IContextInitializer
{
    public void Initialize(TelemetryContext context)
    {
        context.Component.Version = GetApplicationVersion();
    }
}

you can create telemetry initializer that will do the same:

1
2
3
4
5
6
7
8
9
public class AppVersionTelemetryInitializer : ITelemetryInitializer
{
    string applicationVersion = GetApplicationVersion();

    public void Initialize(ITelemetry telemetry)
    {
        telemetry.Context.Component.Version = applicationVersion;
    }
}

Reason 4. Context initializer name is confusing.

If you think what context initializer is doing - it initializes TelemetryClient, not telemetry context. If you’ll construct an instance of TelemetryContext context initializer will not be called. So why is it called context initializer and not client initializer? My telemetry item also have a context. Will context initializer be called when it is constructed? And why not?

The whole reason I’m writing this article is that we regularly getting questions about it and found out that the concept of context initializer if very confusing.

So even if you understood the concept - somebody else may be confused by it and hit a problem with initialization ordering. It is typically hard to troubleshoot why some properties were not added to your telemetry items in production, especially if the issues is timing specific.

The biggest reason we still have a concept of Context Initializer is to keep backward compatibility and do not break existing context initializers you might already have. We are thinking of moving away from context initializers in the future versions and mark this interface as deprecated.