ApplicationInsights-dotnet: Null Ref Exception when InstrumentationKey is null

We are building some middleware for ASP.NET Core which takes advantage of AI’s handling of the Request-Id header. While testing the middleware we wanted to enable AI but did not want to report any telemetry. We therefore created:

var appInsightsOptions =
                new ApplicationInsightsServiceOptions
                {
                    DeveloperMode = Environment.IsDevelopment()
                };

Note that there is no Instrumentation Key. Invoking services.AddApplicationInsightsTelemetry(appInsightsOptions); with these options succeeds. However, our component under test later makes Web Requests via an HttpClient. These invocations result in a NRE with no useful debugging information.

We worked around this issue by adding an empty-string Instrumentation Key to our AI Options:

var appInsightsOptions =
                new ApplicationInsightsServiceOptions
                {
                    DeveloperMode = Environment.IsDevelopment(),
                    InstrumentationKey = ""
                };

Expected:

There are two obvious options:

  • Enable HttpClient to work when AI is enabled but an Instrumentation Key is not provided. This is preferred, since AI seems to work [e.g. it reads an incoming request’s Request-Id header and propagates it to Activity.Current.Id] so long as we don’t invoke an HttpClient.
  • Provide an actionable exception. This would probably be some sort of argument exception when invoking AddApplicationInsightsTelemetry without an Instrumentation Key.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 22 (12 by maintainers)

Commits related to this issue

Most upvoted comments

It seems I have found the root cause for this issue.

It looks like the problem is not AddApplicationInsights not being idempoted - it is. The problem is multiple WebHosts running in the same process. I.e. there are several instances of ServiceProvider and each of them has full and valid set of ApplicationInsights services.

However since there are a lot of singletons (i.e. static instance, not the DI kind of singleton), their lifetime is not limited to the WebHost.

It’s easy to repro with following tests:

  1. Sequential web host creation - seems to work just fine
        public async Task TwoWebHostsSequentially()
        {
            using (var host1 = BuildWebHost(new string[0], 5000))
            {
                Task.Run(() => host1.Run());
                await hc.SendAsync(request);
            }

            using (var host2 = BuildWebHost(new string[0], 5001))
            {
                Task.Run(() => host2.Run());
                await hc.SendAsync(request);
            }
        }
  1. Parallel web host creation - works, however, dependencies are reported twice for each host ( perhaps because of DiagnosticListener subscriptions)
        [Fact]
        public async Task TwoWebHostsInParallel()
        {
            using (var host1 = BuildWebHost(new string[0], 5000))
            using (var host2 = BuildWebHost(new string[0], 5001))
            {
                Task.Run(() => host1.Run());
                Task.Run(() => host2.Run());

                var request1 = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/api/values");
                await hc.SendAsync(request1);

                var request2 = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5001/api/values");
                await hc.SendAsync(request2);
            }
        }
  1. one of the hosts is disposed while another is active
        [Fact]
        public async Task TwoWebHostsAfterOneIsDisposed()
        {
            var host1 = BuildWebHost(new string[0], 5000);
            var host2 = BuildWebHost(new string[0], 5001);

            Task.Run(() => host1.Run());
            Task.Run(() => host2.Run());

            var request1 = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/api/values");
            await hc.SendAsync(request1);
            host1.Dispose();

            var request2 = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5001/api/values");
            await hc.SendAsync(request2);
        }

It results in

Test Name:	testrepro.UnitTest1.TwoWebHostsAfterOneIsDisposed
Test FullName:	testrepro.UnitTest1.TwoWebHostsAfterOneIsDisposed
Test Source:	c:\repo\delme\aspnetcore\testrepro\UnitTest1.cs : line 71

Result StackTrace:	
at Microsoft.ApplicationInsights.DependencyCollector.Implementation.ApplicationInsightsUrlFilter.get_EndpointLeftPart()
   at Microsoft.ApplicationInsights.DependencyCollector.Implementation.ApplicationInsightsUrlFilter.IsApplicationInsightsUrlImpl(String url)
   at Microsoft.ApplicationInsights.DependencyCollector.Implementation.HttpCoreDiagnosticSourceListener.HttpCoreDiagnosticSourceSubscriber.<OnNext>b__6_0(String evnt, Object r, Object _)
   at System.Diagnostics.DiagnosticListener.IsEnabled(String name, Object arg1, Object arg2)
   at System.Net.Http.DiagnosticsHandler.<SendAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.HttpClient.<FinishSendAsyncBuffered>d__58.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at testrepro.UnitTest1.<TwoWebHostsAfterOneIsDisposed>d__5.MoveNext() in c:\repo\delme\aspnetcore\testrepro\UnitTest1.cs:line 103
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
Result Message:	System.NullReferenceException : Object reference not set to an instance of an object.


@mkosieradzki I believe that what’s happen in your case. Could you confirm?

Root cause:

TelemetryConfiguration being a singleton (static instance) is reused by host2. When the host1 is disposed, it causes TelemetyConfiguration instance to be disposed. This results in TelemetryChannel underneath to be disposed and set to null

So when app insights in host2 attempt to track dependency, it leads to the call to null telemetry channel.