Adding telemetry publish for unhandled exceptions to developer exception page and exception handler #180

This commit is contained in:
John Luo 2015-10-02 10:43:48 -07:00
parent 3657a1a14b
commit 7b9cfac65a
7 changed files with 189 additions and 5 deletions

View File

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Globalization;
using System.IO;
using System.Linq;
@ -29,6 +30,7 @@ namespace Microsoft.AspNet.Diagnostics
private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null;
private readonly ILogger _logger;
private readonly IFileProvider _fileProvider;
private readonly TelemetrySource _telemetrySource;
/// <summary>
/// Initializes a new instance of the <see cref="DeveloperExceptionPageMiddleware"/> class
@ -39,7 +41,8 @@ namespace Microsoft.AspNet.Diagnostics
RequestDelegate next,
ErrorPageOptions options,
ILoggerFactory loggerFactory,
IApplicationEnvironment appEnvironment)
IApplicationEnvironment appEnvironment,
TelemetrySource telemetrySource)
{
if (next == null)
{
@ -55,6 +58,7 @@ namespace Microsoft.AspNet.Diagnostics
_options = options;
_logger = loggerFactory.CreateLogger<DeveloperExceptionPageMiddleware>();
_fileProvider = options.FileProvider ?? new PhysicalFileProvider(appEnvironment.ApplicationBasePath);
_telemetrySource = telemetrySource;
}
/// <summary>
@ -72,6 +76,7 @@ namespace Microsoft.AspNet.Diagnostics
catch (Exception ex)
{
_logger.LogError("An unhandled exception has occurred while executing the request", ex);
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started, the error page middleware will not be executed.");
@ -84,6 +89,9 @@ namespace Microsoft.AspNet.Diagnostics
context.Response.StatusCode = 500;
await DisplayException(context, ex);
_telemetrySource.WriteTelemetry("Microsoft.AspNet.Diagnostics.UnhandledException", new { httpContext = context, exception = ex });
return;
}
catch (Exception ex2)

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics.Tracing;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
@ -17,8 +18,13 @@ namespace Microsoft.AspNet.Diagnostics
private readonly ExceptionHandlerOptions _options;
private readonly ILogger _logger;
private readonly Func<object, Task> _clearCacheHeadersDelegate;
private readonly TelemetrySource _telemetrySource;
public ExceptionHandlerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, ExceptionHandlerOptions options)
public ExceptionHandlerMiddleware(
RequestDelegate next,
ILoggerFactory loggerFactory,
ExceptionHandlerOptions options,
TelemetrySource telemetrySource)
{
_next = next;
_options = options;
@ -28,6 +34,7 @@ namespace Microsoft.AspNet.Diagnostics
_options.ExceptionHandler = _next;
}
_clearCacheHeadersDelegate = ClearCacheHeaders;
_telemetrySource = telemetrySource;
}
public async Task Invoke(HttpContext context)
@ -63,6 +70,9 @@ namespace Microsoft.AspNet.Diagnostics
context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);
await _options.ExceptionHandler(context);
_telemetrySource.WriteTelemetry("Microsoft.AspNet.Diagnostics.HandledException", new { httpContext = context, exception = ex });
// TODO: Optional re-throw? We'll re-throw the original exception by default if the error handler throws.
return;
}

View File

@ -13,10 +13,15 @@
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
"Microsoft.Extensions.OptionsModel": "1.0.0-*",
"Microsoft.Dnx.Compilation.Abstractions": "1.0.0-*",
"Microsoft.Extensions.WebEncoders.Core": "1.0.0-*"
"Microsoft.Extensions.WebEncoders.Core": "1.0.0-*",
"System.Diagnostics.Tracing.Telemetry": "4.0.0-beta-*"
},
"frameworks": {
"dnx451": {},
"dnx451": {
"frameworkAssemblies": {
"System.Runtime": ""
}
},
"dnxcore50": {
"dependencies": {
"System.Reflection.Extensions": "4.0.1-beta-*"

View File

@ -3,16 +3,20 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Diagnostics.Views;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.TestHost;
using Microsoft.AspNet.Testing;
using Microsoft.Dnx.Runtime;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Xunit;
@ -298,7 +302,8 @@ namespace Microsoft.AspNet.Diagnostics
(httpContext) => { return Task.FromResult(0); },
errorPageOptions,
new LoggerFactory(),
new TestApplicationEnvironment());
new TestApplicationEnvironment(),
new TelemetryListener("Microsoft.Aspnet"));
return middleware;
}
@ -471,5 +476,35 @@ namespace Microsoft.AspNet.Diagnostics
public IEnumerable<string> ExpectedErrorCode { get; set; }
public IEnumerable<string> ExpectedPostErrorCode { get; set; }
}
[Fact]
public async Task UnhandledErrorsWriteToDiagnosticTelemetryWhenUsingExceptionPage()
{
// Arrange
TelemetryListener telemetryListener = null;
var server = TestServer.Create(app =>
{
telemetryListener = app.ApplicationServices.GetRequiredService<TelemetryListener>();
app.UseDeveloperExceptionPage();
app.Run(context =>
{
throw new Exception("Test exception");
});
});
var listener = new TestTelemetryListener();
telemetryListener.SubscribeWithAdapter(listener);
// Act
await server.CreateClient().GetAsync("/path");
// Assert
Assert.NotNull(listener.EndRequest?.HttpContext);
Assert.Null(listener.HostingUnhandledException?.HttpContext);
Assert.Null(listener.HostingUnhandledException?.Exception);
Assert.NotNull(listener.DiagnosticUnhandledException?.HttpContext);
Assert.NotNull(listener.DiagnosticUnhandledException?.Exception);
Assert.Null(listener.DiagnosticHandledException?.HttpContext);
Assert.Null(listener.DiagnosticHandledException?.Exception);
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.IO;
using System.Linq;
using System.Net;
@ -10,6 +11,7 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.AspNet.Diagnostics
@ -321,5 +323,45 @@ namespace Microsoft.AspNet.Diagnostics
Assert.Equal("abcdef", values.First());
}
}
[Fact]
public async Task HandledErrorsWriteToDiagnosticTelemetryWhenUsingExceptionHandler()
{
// Arrange
TelemetryListener telemetryListener = null;
var server = TestServer.Create(app =>
{
telemetryListener = app.ApplicationServices.GetRequiredService<TelemetryListener>();
app.UseExceptionHandler("/handle-errors");
app.Map("/handle-errors", (innerAppBuilder) =>
{
innerAppBuilder.Run(async (httpContext) =>
{
await httpContext.Response.WriteAsync("Handled error in a custom way.");
});
});
app.Run(context =>
{
throw new Exception("Test exception");
});
});
var listener = new TestTelemetryListener();
telemetryListener.SubscribeWithAdapter(listener);
// Act
await server.CreateClient().GetAsync(string.Empty);
// Assert
Assert.NotNull(listener.EndRequest?.HttpContext);
Assert.Null(listener.HostingUnhandledException?.HttpContext);
Assert.Null(listener.HostingUnhandledException?.Exception);
Assert.Null(listener.DiagnosticUnhandledException?.HttpContext);
Assert.Null(listener.DiagnosticUnhandledException?.Exception);
Assert.NotNull(listener.DiagnosticHandledException?.HttpContext);
Assert.NotNull(listener.DiagnosticHandledException?.Exception);
}
}
}

View File

@ -0,0 +1,83 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.TelemetryAdapter;
namespace Microsoft.AspNet.Diagnostics
{
public class TestTelemetryListener
{
public class OnRequestEventData
{
public IProxyHttpContext HttpContext { get; set; }
}
public class OnExceptionEventData
{
public IProxyHttpContext HttpContext { get; set; }
public IProxyException Exception { get; set; }
}
public OnRequestEventData BeginRequest { get; set; }
public OnRequestEventData EndRequest { get; set; }
public OnExceptionEventData HostingUnhandledException { get; set; }
public OnExceptionEventData DiagnosticUnhandledException { get; set; }
public OnExceptionEventData DiagnosticHandledException { get; set; }
[TelemetryName("Microsoft.AspNet.Hosting.BeginRequest")]
public virtual void OnBeginRequest(IProxyHttpContext httpContext)
{
BeginRequest = new OnRequestEventData()
{
HttpContext = httpContext
};
}
[TelemetryName("Microsoft.AspNet.Hosting.EndRequest")]
public virtual void OnEndRequest(IProxyHttpContext httpContext)
{
EndRequest = new OnRequestEventData()
{
HttpContext = httpContext
};
}
[TelemetryName("Microsoft.AspNet.Hosting.UnhandledException")]
public virtual void OnHostingUnhandledException(IProxyHttpContext httpContext, IProxyException exception)
{
HostingUnhandledException = new OnExceptionEventData()
{
HttpContext = httpContext,
Exception = exception
};
}
[TelemetryName("Microsoft.AspNet.Diagnostics.UnhandledException")]
public virtual void OnDiagnosticUnhandledException(IProxyHttpContext httpContext, IProxyException exception)
{
DiagnosticUnhandledException = new OnExceptionEventData()
{
HttpContext = httpContext,
Exception = exception
};
}
[TelemetryName("Microsoft.AspNet.Diagnostics.HandledException")]
public virtual void OnDiagnosticHandledException(IProxyHttpContext httpContext, IProxyException exception)
{
DiagnosticHandledException = new OnExceptionEventData()
{
HttpContext = httpContext,
Exception = exception
};
}
public interface IProxyHttpContext
{
}
public interface IProxyException
{
}
}
}

View File

@ -8,6 +8,7 @@
"Microsoft.AspNet.TestHost": "1.0.0-*",
"Microsoft.AspNet.Testing": "1.0.0-*",
"Microsoft.Extensions.DependencyInjection": "1.0.0-*",
"Microsoft.Extensions.TelemetryAdapter": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
},