diff --git a/src/LanguageServer/Impl/LanguageServer.cs b/src/LanguageServer/Impl/LanguageServer.cs index 7c09d1cd2..001a9595a 100644 --- a/src/LanguageServer/Impl/LanguageServer.cs +++ b/src/LanguageServer/Impl/LanguageServer.cs @@ -45,6 +45,7 @@ public sealed partial class LanguageServer : IDisposable { private IServiceContainer _services; private IUIService _ui; + private ITelemetryService2 _telemetry; private JsonRpc _rpc; private JsonSerializer _jsonSerializer; @@ -60,10 +61,11 @@ public CancellationToken Start(IServiceContainer services, JsonRpc rpc) { _ui = services.GetService(); _rpc = rpc; _jsonSerializer = services.GetService(); + _telemetry = services.GetService(); var progress = services.GetService(); - var rpcTraceListener = new TelemetryRpcTraceListener(services.GetService()); + var rpcTraceListener = new TelemetryRpcTraceListener(_telemetry); _rpc.TraceSource.Listeners.Add(rpcTraceListener); _server.OnLogMessage += OnLogMessage; @@ -72,6 +74,7 @@ public CancellationToken Start(IServiceContainer services, JsonRpc rpc) { _server.OnApplyWorkspaceEdit += OnApplyWorkspaceEdit; _server.OnRegisterCapability += OnRegisterCapability; _server.OnUnregisterCapability += OnUnregisterCapability; + _server.AnalysisQueue.UnhandledException += OnAnalysisQueueUnhandledException; _disposables .Add(() => _server.OnLogMessage -= OnLogMessage) @@ -80,6 +83,7 @@ public CancellationToken Start(IServiceContainer services, JsonRpc rpc) { .Add(() => _server.OnApplyWorkspaceEdit -= OnApplyWorkspaceEdit) .Add(() => _server.OnRegisterCapability -= OnRegisterCapability) .Add(() => _server.OnUnregisterCapability -= OnUnregisterCapability) + .Add(() => _server.AnalysisQueue.UnhandledException -= OnAnalysisQueueUnhandledException) .Add(() => _shutdownCts.Cancel()) .Add(_prioritizer) .Add(() => _pathsWatcher?.Dispose()) @@ -454,6 +458,16 @@ private void HandlePathWatchChange(JToken section) { _searchPaths = _initParams.initializationOptions.searchPaths; } + private void OnAnalysisQueueUnhandledException(object sender, UnhandledExceptionEventArgs e) { + if (!(e.ExceptionObject is Exception ex)) { + Debug.Fail($"ExceptionObject was {e.ExceptionObject.GetType()}, not Exception"); + return; + } + + var te = Telemetry.CreateEventWithException("analysis_queue.unhandled_exception", ex); + _telemetry.SendTelemetry(te).DoNotWait(); + } + private class Prioritizer : IDisposable { private const int InitializePriority = 0; private const int ConfigurationPriority = 1; diff --git a/src/LanguageServer/Impl/Telemetry.cs b/src/LanguageServer/Impl/Telemetry.cs index d59da4df0..ee0592c70 100644 --- a/src/LanguageServer/Impl/Telemetry.cs +++ b/src/LanguageServer/Impl/Telemetry.cs @@ -27,6 +27,26 @@ internal class Telemetry { public static TelemetryEvent CreateEvent(string eventName) => new TelemetryEvent { EventName = EventPrefix + eventName, }; + + public static TelemetryEvent CreateEventWithException(string eventName, Exception e) { + var te = CreateEvent(eventName); + + if (e is AggregateException ae && ae.InnerException != null) { + te.Properties["outerName"] = e.GetType().Name; + te.Properties["outerStackTrace"] = e.StackTrace; + + e = ae.Flatten(); + + while (e.InnerException != null) { + e = e.InnerException; + } + } + + te.Properties["name"] = e.GetType().Name; + te.Properties["stackTrace"] = e.StackTrace; + + return te; + } } internal class TelemetryRpcTraceListener : TraceListener { @@ -72,10 +92,8 @@ public override void TraceData(TraceEventCache eventCache, string source, TraceE return; } - var e = Telemetry.CreateEvent("rpc.exception"); + var e = Telemetry.CreateEventWithException("rpc.exception", exception); e.Properties["method"] = method; - e.Properties["name"] = exception.GetType().Name; - e.Properties["stackTrace"] = exception.StackTrace; _telemetryService.SendTelemetry(e).DoNotWait(); }