Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/Releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ This page tracks major changes included in any update starting with version 4.0.
- Generally moves to CSS 3 variables, for easier custom themes as well ([#451](https://github.com/MiniProfiler/dotnet/pull/451))
- Added `SqlServerFormatter.IncludeParameterValues` for excluding actual values in output if desired ([#463](https://github.com/MiniProfiler/dotnet/pull/463))
- Added "debug" mode (via `.EnableDebugMode`) that outputs stack dumps for every timing (expensive/heavy, and not intended for normal operation - [#482](https://github.com/MiniProfiler/dotnet/pull/482))
- Added `.OnInternalError` API to options (`Action<Exception>`), for logging when an exception during a MiniProfiler occurs ([#486](https://github.com/MiniProfiler/dotnet/pull/486)).
- This also means save errors are "swallowed" now (and accessible via this API when desired).
- (**.NET Core only**) Added `MiniProfilerOptions.ResultsAuthorizeAsync` and `MiniProfiler.ResultsAuthorizeListAsync` ([#472](https://github.com/MiniProfiler/dotnet/pull/472))
- (**.NET Core only**) Added profiling to all diagnostic events (views, filters, etc. - [#475](https://github.com/MiniProfiler/dotnet/pull/475) & [#482](https://github.com/MiniProfiler/dotnet/pull/482))
- New options around this are in the ASP.NET Core docs on the left.
Expand Down
167 changes: 89 additions & 78 deletions src/MiniProfiler.AspNetCore.Mvc/MvcDiagnosticListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,85 +136,96 @@ private object Complete<T>(T state) where T : class
/// Provides the observer with new data.
/// </summary>
/// <param name="kv">The current notification information.</param>
public void OnNext(KeyValuePair<string, object> kv) => _ = kv.Value switch
public void OnNext(KeyValuePair<string, object> kv)
{
// MVC Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Core/src/Diagnostics/MvcDiagnostics.cs
// ActionEvent
BeforeActionEventData data => Start(data.ActionDescriptor, GetName("Action", data.ActionDescriptor)),
AfterActionEventData data => Complete(data.ActionDescriptor),
// ControllerActionMethod
BeforeControllerActionMethodEventData data => Start(data.ActionContext.ActionDescriptor, GetName("Controller Action", data.ActionContext.ActionDescriptor)),
AfterControllerActionMethodEventData data => Complete(data.ActionContext.ActionDescriptor),
// ActionResultEvent
BeforeActionResultEventData data => Start(data.Result, GetName(data.Result)),
AfterActionResultEventData data => Complete(data.Result),

// AuthorizationFilterOnAuthorization
BeforeAuthorizationFilterOnAuthorizationEventData data => StartFilter(data.Filter, "Auth Filter: " + GetName(data.Filter)),
AfterAuthorizationFilterOnAuthorizationEventData data => Complete(data.Filter),

// ResourceFilterOnResourceExecution
BeforeResourceFilterOnResourceExecutionEventData data => StartFilter(data.Filter, "Resource Filter (Exec): " + GetName(data.Filter)),
AfterResourceFilterOnResourceExecutionEventData data => Complete(data.Filter),
// ResourceFilterOnResourceExecuting
BeforeResourceFilterOnResourceExecutingEventData data => StartFilter(data.Filter, "Resource Filter (Execing): " + GetName(data.Filter)),
AfterResourceFilterOnResourceExecutingEventData data => Complete(data.Filter),
// ResourceFilterOnResourceExecuted
BeforeResourceFilterOnResourceExecutedEventData data => StartFilter(data.Filter, "Resource Filter (Execed): " + GetName(data.Filter)),
AfterResourceFilterOnResourceExecutedEventData data => Complete(data.Filter),

// ExceptionFilterOnException
BeforeExceptionFilterOnException data => StartFilter(data.Filter, "Exception Filter: " + GetName(data.Filter)),
AfterExceptionFilterOnExceptionEventData data => Complete(data.Filter),

// ActionFilterOnActionExecution
BeforeActionFilterOnActionExecutionEventData data => StartFilter(data.Filter, "Action Filter (Exec): " + GetName(data.Filter)),
AfterActionFilterOnActionExecutionEventData data => Complete(data.Filter),
// ActionFilterOnActionExecuting
BeforeActionFilterOnActionExecutingEventData data => StartFilter(data.Filter, "Action Filter (Execing): " + GetName(data.Filter)),
AfterActionFilterOnActionExecutingEventData data => Complete(data.Filter),
// ActionFilterOnActionExecuted
BeforeActionFilterOnActionExecutedEventData data => StartFilter(data.Filter, "Action Filter (Execed): " + GetName(data.Filter)),
AfterActionFilterOnActionExecutedEventData data => Complete(data.Filter),

// ResultFilterOnResultExecution
BeforeResultFilterOnResultExecutionEventData data => StartFilter(data.Filter, "Result Filter (Exec): " + GetName(data.Filter)),
AfterResultFilterOnResultExecutionEventData data => Complete(data.Filter),
// ResultFilterOnResultExecuting
BeforeResultFilterOnResultExecutingEventData data => StartFilter(data.Filter, "Result Filter (Execing): " + GetName(data.Filter)),
AfterResultFilterOnResultExecutingEventData data => Complete(data.Filter),
// ResultFilterOnResultExecuted
BeforeResultFilterOnResultExecutedEventData data => StartFilter(data.Filter, "Result Filter (Execed): " + GetName(data.Filter)),
AfterResultFilterOnResultExecutedEventData data => Complete(data.Filter),

// Razor Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Razor/src/Diagnostics/MvcDiagnostics.cs
// ViewPage
BeforeViewPageEventData data => StartView(data.Page, "View: " + data.Page.Path),
AfterViewPageEventData data => Complete(data.Page),

// RazorPage Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.RazorPages/src/Diagnostics/MvcDiagnostics.cs
// HandlerMethod
BeforeHandlerMethodEventData data => Start(data.Instance, "Handler: " + data.HandlerMethodDescriptor.Name),
AfterHandlerMethodEventData data => Complete(data.Instance),

// PageFilterOnPageHandlerExecution
BeforePageFilterOnPageHandlerExecutionEventData data => StartFilter(data.Filter, "Filter (Exec): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerExecutionEventData data => Complete(data.Filter),
// PageFilterOnPageHandlerExecuting
BeforePageFilterOnPageHandlerExecutingEventData data => StartFilter(data.Filter, "Filter (Execing): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerExecutingEventData data => Complete(data.Filter),
// PageFilterOnPageHandlerExecuted
BeforePageFilterOnPageHandlerExecutedEventData data => StartFilter(data.Filter, "Filter (Execed): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerExecutedEventData data => Complete(data.Filter),

// PageFilterOnPageHandlerSelection
BeforePageFilterOnPageHandlerSelectionEventData data => StartFilter(data.Filter, "Filter (Selection): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerSelectionEventData data => Complete(data.Filter),
// PageFilterOnPageHandlerSelected
BeforePageFilterOnPageHandlerSelectedEventData data => StartFilter(data.Filter, "Filter (Selected): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerSelectedEventData data => Complete(data.Filter),
_ => null
};
try
{
_ = kv.Value switch
{
// MVC Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Core/src/Diagnostics/MvcDiagnostics.cs
// ActionEvent
BeforeActionEventData data => Start(data.ActionDescriptor, GetName("Action", data.ActionDescriptor)),
AfterActionEventData data => Complete(data.ActionDescriptor),
// ControllerActionMethod
BeforeControllerActionMethodEventData data => Start(data.ActionContext.ActionDescriptor, GetName("Controller Action", data.ActionContext.ActionDescriptor)),
AfterControllerActionMethodEventData data => Complete(data.ActionContext.ActionDescriptor),
// ActionResultEvent
BeforeActionResultEventData data => Start(data.Result, GetName(data.Result)),
AfterActionResultEventData data => Complete(data.Result),

// AuthorizationFilterOnAuthorization
BeforeAuthorizationFilterOnAuthorizationEventData data => StartFilter(data.Filter, "Auth Filter: " + GetName(data.Filter)),
AfterAuthorizationFilterOnAuthorizationEventData data => Complete(data.Filter),

// ResourceFilterOnResourceExecution
BeforeResourceFilterOnResourceExecutionEventData data => StartFilter(data.Filter, "Resource Filter (Exec): " + GetName(data.Filter)),
AfterResourceFilterOnResourceExecutionEventData data => Complete(data.Filter),
// ResourceFilterOnResourceExecuting
BeforeResourceFilterOnResourceExecutingEventData data => StartFilter(data.Filter, "Resource Filter (Execing): " + GetName(data.Filter)),
AfterResourceFilterOnResourceExecutingEventData data => Complete(data.Filter),
// ResourceFilterOnResourceExecuted
BeforeResourceFilterOnResourceExecutedEventData data => StartFilter(data.Filter, "Resource Filter (Execed): " + GetName(data.Filter)),
AfterResourceFilterOnResourceExecutedEventData data => Complete(data.Filter),

// ExceptionFilterOnException
BeforeExceptionFilterOnException data => StartFilter(data.Filter, "Exception Filter: " + GetName(data.Filter)),
AfterExceptionFilterOnExceptionEventData data => Complete(data.Filter),

// ActionFilterOnActionExecution
BeforeActionFilterOnActionExecutionEventData data => StartFilter(data.Filter, "Action Filter (Exec): " + GetName(data.Filter)),
AfterActionFilterOnActionExecutionEventData data => Complete(data.Filter),
// ActionFilterOnActionExecuting
BeforeActionFilterOnActionExecutingEventData data => StartFilter(data.Filter, "Action Filter (Execing): " + GetName(data.Filter)),
AfterActionFilterOnActionExecutingEventData data => Complete(data.Filter),
// ActionFilterOnActionExecuted
BeforeActionFilterOnActionExecutedEventData data => StartFilter(data.Filter, "Action Filter (Execed): " + GetName(data.Filter)),
AfterActionFilterOnActionExecutedEventData data => Complete(data.Filter),

// ResultFilterOnResultExecution
BeforeResultFilterOnResultExecutionEventData data => StartFilter(data.Filter, "Result Filter (Exec): " + GetName(data.Filter)),
AfterResultFilterOnResultExecutionEventData data => Complete(data.Filter),
// ResultFilterOnResultExecuting
BeforeResultFilterOnResultExecutingEventData data => StartFilter(data.Filter, "Result Filter (Execing): " + GetName(data.Filter)),
AfterResultFilterOnResultExecutingEventData data => Complete(data.Filter),
// ResultFilterOnResultExecuted
BeforeResultFilterOnResultExecutedEventData data => StartFilter(data.Filter, "Result Filter (Execed): " + GetName(data.Filter)),
AfterResultFilterOnResultExecutedEventData data => Complete(data.Filter),

// Razor Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.Razor/src/Diagnostics/MvcDiagnostics.cs
// ViewPage
BeforeViewPageEventData data => StartView(data.Page, "View: " + data.Page.Path),
AfterViewPageEventData data => Complete(data.Page),

// RazorPage Bits: https://github.com/dotnet/aspnetcore/blob/v3.0.0/src/Mvc/Mvc.RazorPages/src/Diagnostics/MvcDiagnostics.cs
// HandlerMethod
BeforeHandlerMethodEventData data => Start(data.Instance, "Handler: " + data.HandlerMethodDescriptor.Name),
AfterHandlerMethodEventData data => Complete(data.Instance),

// PageFilterOnPageHandlerExecution
BeforePageFilterOnPageHandlerExecutionEventData data => StartFilter(data.Filter, "Filter (Exec): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerExecutionEventData data => Complete(data.Filter),
// PageFilterOnPageHandlerExecuting
BeforePageFilterOnPageHandlerExecutingEventData data => StartFilter(data.Filter, "Filter (Execing): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerExecutingEventData data => Complete(data.Filter),
// PageFilterOnPageHandlerExecuted
BeforePageFilterOnPageHandlerExecutedEventData data => StartFilter(data.Filter, "Filter (Execed): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerExecutedEventData data => Complete(data.Filter),

// PageFilterOnPageHandlerSelection
BeforePageFilterOnPageHandlerSelectionEventData data => StartFilter(data.Filter, "Filter (Selection): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerSelectionEventData data => Complete(data.Filter),
// PageFilterOnPageHandlerSelected
BeforePageFilterOnPageHandlerSelectedEventData data => StartFilter(data.Filter, "Filter (Selected): " + GetName(data.Filter)),
AfterPageFilterOnPageHandlerSelectedEventData data => Complete(data.Filter),
_ => null
};
}
catch (Exception ex)
{
// Don't error in profiling here, just flow it out
MiniProfiler.DefaultOptions?.OnInternalError?.Invoke(ex);
}
}
}
}
#endif
5 changes: 5 additions & 0 deletions src/MiniProfiler.Shared/Internal/MiniProfilerBaseOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ public class MiniProfilerBaseOptions
/// </summary>
protected virtual void OnConfigure() { }

/// <summary>
/// An action to call when MiniProfiler has an internal error. For logging, etc.
/// </summary>
public Action<Exception> OnInternalError { get; set; }

internal void Configure() => OnConfigure();
}
}
18 changes: 16 additions & 2 deletions src/MiniProfiler.Shared/MiniProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,14 @@ public bool Stop(bool discardResults = false)
{
return false;
}
Options.ProfilerProvider.Stopped(this, discardResults);
try
{
Options.ProfilerProvider.Stopped(this, discardResults);
}
catch (Exception ex)
{
Options.OnInternalError?.Invoke(ex);
}
return true;
}

Expand All @@ -241,7 +248,14 @@ public async Task<bool> StopAsync(bool discardResults = false)
{
return false;
}
await Options.ProfilerProvider.StoppedAsync(this, discardResults).ConfigureAwait(false);
try
{
await Options.ProfilerProvider.StoppedAsync(this, discardResults).ConfigureAwait(false);
}
catch (Exception ex)
{
Options.OnInternalError?.Invoke(ex);
}
return true;
}

Expand Down
66 changes: 66 additions & 0 deletions tests/MiniProfiler.Tests/InternalErrorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using StackExchange.Profiling.Internal;
using StackExchange.Profiling.Storage;
using Xunit;
using Xunit.Abstractions;

namespace StackExchange.Profiling.Tests
{
public class InternalErrorTests : BaseTest
{
public InternalErrorTests(ITestOutputHelper output) : base(output) { }

[Fact]
public async Task StopErrorLogging()
{
int errorCount = 0;
Exception lastError = null;
void Log(Exception ex)
{
errorCount++;
lastError = ex;
}
var options = new MiniProfilerBaseOptions()
{
Storage = new KaboomStorage(),
StopwatchProvider = () => new UnitTestStopwatch(),
OnInternalError = Log
};

var profiler = options.StartProfiler();
AddRecursiveChildren(profiler, 1, 10);
Assert.Equal(0, errorCount);
profiler.Stop();
Assert.Equal(1, errorCount);
Assert.IsType<KaboomStorage.BoomBoom>(lastError);

profiler = options.StartProfiler();
AddRecursiveChildren(profiler, 1, 10);
Assert.Equal(1, errorCount);
await profiler.StopAsync().ConfigureAwait(false);
Assert.Equal(2, errorCount);
Assert.IsType<KaboomStorage.BoomBoom>(lastError);
}
}


public class KaboomStorage : IAsyncStorage
{
public List<Guid> GetUnviewedIds(string user) => throw new BoomBoom();
public Task<List<Guid>> GetUnviewedIdsAsync(string user) => throw new BoomBoom();
public IEnumerable<Guid> List(int maxResults, DateTime? start = null, DateTime? finish = null, ListResultsOrder orderBy = ListResultsOrder.Descending) => throw new BoomBoom();
public Task<IEnumerable<Guid>> ListAsync(int maxResults, DateTime? start = null, DateTime? finish = null, ListResultsOrder orderBy = ListResultsOrder.Descending) => throw new BoomBoom();
public MiniProfiler Load(Guid id) => throw new BoomBoom();
public Task<MiniProfiler> LoadAsync(Guid id) => throw new BoomBoom();
public void Save(MiniProfiler profiler) => throw new BoomBoom();
public Task SaveAsync(MiniProfiler profiler) => throw new BoomBoom();
public void SetUnviewed(string user, Guid id) => throw new BoomBoom();
public Task SetUnviewedAsync(string user, Guid id) => throw new BoomBoom();
public void SetViewed(string user, Guid id) => throw new BoomBoom();
public Task SetViewedAsync(string user, Guid id) => throw new BoomBoom();

public class BoomBoom : Exception { }
}
}