Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;

namespace Microsoft.AspNetCore.Razor.Telemetry;

internal interface IFaultExceptionHandler
{
bool HandleException(ITelemetryReporter reporter, Exception exception, string? message, object?[] @params);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ public interface ITelemetryReporter
{
void ReportEvent(string name, TelemetrySeverity severity);
void ReportEvent<T>(string name, TelemetrySeverity severity, ImmutableDictionary<string, T> values);
void ReportFault(Exception exception, string? message, object[] @params);
void ReportFault(Exception exception, string? message, params object?[] @params);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ namespace Microsoft.AspNetCore.Razor.Telemetry;
internal class TelemetryReporter : ITelemetryReporter
{
private readonly ImmutableArray<TelemetrySession> _telemetrySessions;
private readonly IEnumerable<IFaultExceptionHandler> _faultExceptionHandlers;
private readonly ILogger? _logger;

[ImportingConstructor]
public TelemetryReporter([Import(AllowDefault = true)] ILoggerFactory? loggerFactory = null)
: this(ImmutableArray.Create(TelemetryService.DefaultSession), loggerFactory)
public TelemetryReporter(
[Import(AllowDefault = true)] ILoggerFactory? loggerFactory = null,
[ImportMany] IEnumerable<IFaultExceptionHandler>? faultExceptionHandlers = null)
: this(ImmutableArray.Create(TelemetryService.DefaultSession), loggerFactory, faultExceptionHandlers)
{
}

public TelemetryReporter(ImmutableArray<TelemetrySession> telemetrySessions, ILoggerFactory? loggerFactory)
public TelemetryReporter(ImmutableArray<TelemetrySession> telemetrySessions, ILoggerFactory? loggerFactory, IEnumerable<IFaultExceptionHandler>? faultExceptionHandlers)
{
_telemetrySessions = telemetrySessions;
_faultExceptionHandlers = faultExceptionHandlers ?? Array.Empty<IFaultExceptionHandler>();
_logger = loggerFactory?.CreateLogger<TelemetryReporter>();
}

Expand All @@ -48,7 +52,7 @@ public void ReportEvent<T>(string name, TelemetrySeverity severity, ImmutableDic
Report(telemetryEvent);
}

public void ReportFault(Exception exception, string? message, object[] @params)
public void ReportFault(Exception exception, string? message, params object?[] @params)
{
try
{
Expand All @@ -69,6 +73,20 @@ public void ReportFault(Exception exception, string? message, object[] @params)
return;
}

var handled = false;
foreach (var handler in _faultExceptionHandlers)
{
if (handler.HandleException(this, exception, message, @params))
{
handled = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first thought was that we should stop iterating after it's been handled, but we could easily handle it twice (one to log and one to attempt recovery perhaps?), and so long as there aren't a ton of Exception handlers it shouldn't matter too much. No real suggestion, just putting some thoughts out there in case you can make better use of them than me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I wasn't sure what the best behavior here would be. I think it's just a best guess until we add more

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. It might be worth adding a comment so that we remember that we weren't quite sure about this behavior when it was written.

}
}

if (handled)
{
return;
}

var currentProcess = Process.GetCurrentProcess();

var faultEvent = new FaultEvent(
Expand All @@ -78,6 +96,16 @@ public void ReportFault(Exception exception, string? message, object[] @params)
exceptionObject: exception,
gatherEventDetails: faultUtility =>
{
foreach (var data in @params)
{
if (data is null)
{
continue;
}

faultUtility.AddErrorInformation(data.ToString());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats a faultUtility and what does it do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of the Fault API we consume from VS

}

// Returning "0" signals that, if sampled, we should send data to Watson.
// Any other value will cancel the Watson report. We never want to trigger a process dump manually,
// we'll let TargetedNotifications determine if a dump should be collected.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public void ReportEvent<T>(string name, TelemetrySeverity severity, ImmutableDic
{
}

public void ReportFault(Exception exception, string? message, object[] @params)
public void ReportFault(Exception exception, string? message, params object?[] @params)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using Microsoft.AspNetCore.Razor.Telemetry;
using StreamJsonRpc;

namespace Microsoft.AspNetCore.Razor.LanguageServer;

internal class JsonRPCFaultExceptionHandler : IFaultExceptionHandler
{
public bool HandleException(ITelemetryReporter reporter, Exception exception, string? message, object?[] @params)
{
if (exception is not RemoteInvocationException remoteInvocationException)
{
return false;
}

reporter.ReportFault(remoteInvocationException, remoteInvocationException.Message, remoteInvocationException.ErrorCode, remoteInvocationException.ErrorData, remoteInvocationException.DeserializedErrorData);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the actual exception give us no useful information at all? I would have thought we'd somehow want to combine all of the info together.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, and we're still keeping that info. We're just adding more now

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,16 @@ protected override ILspServices ConstructLspServices()
// Folding Range Providers
services.AddSingleton<RazorFoldingRangeProvider, RazorCodeBlockFoldingProvider>();

services.AddSingleton<IFaultExceptionHandler, JsonRPCFaultExceptionHandler>();

// Get the DefaultSession for telemetry. This is set by VS with
// TelemetryService.SetDefaultSession and provides the correct
// appinsights keys etc
services.AddSingleton<ITelemetryReporter>(provider =>
new TelemetryReporter(ImmutableArray.Create(TelemetryService.DefaultSession), provider.GetRequiredService<ILoggerFactory>()));
new TelemetryReporter(
ImmutableArray.Create(TelemetryService.DefaultSession),
provider.GetRequiredService<ILoggerFactory>(),
provider.GetServices<IFaultExceptionHandler>()));

// Defaults: For when the caller hasn't provided them through the `configure` action.
services.TryAddSingleton<HostServicesProvider, DefaultHostServicesProvider>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void ReportEvent<T>(string name, TelemetrySeverity severity, ImmutableDic
{
}

public void ReportFault(Exception exception, string? message, object[] @params)
public void ReportFault(Exception exception, string? message, params object?[] @params)
{
}
}