Skip to content

Commit bcb02e6

Browse files
authored
Throw an exception in OOP if an error is logged. (#12376)
Fixes the first part of #12223 We used to log messages to the output window in VS when formatting was being abandoned due to bad code. With cohosting that got lost, as the log messages are now in the service hub log files only. This resurrects them and makes them into an info bar, by virtue of the exception handling in Roslyn, so users will know what is going on.
2 parents 1b3e4df + d062828 commit bcb02e6

File tree

6 files changed

+112
-29
lines changed

6 files changed

+112
-29
lines changed

src/Razor/src/Microsoft.CodeAnalysis.Razor.CohostingShared/HtmlDocumentServices/HtmlDocumentSynchronizer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ private async Task<SynchronizationResult> PublishHtmlDocumentAsync(TextDocument
174174

175175
return result;
176176
}
177+
catch (OperationCanceledException)
178+
{
179+
_logger.LogDebug($"Not publishing Html text for {document.FilePath} as the request was cancelled.");
180+
return default;
181+
}
177182
catch (Exception ex)
178183
{
179184
_logger.LogError(ex, $"Error publishing Html text for {document.FilePath}. Html document contents will be stale");

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/FormattingContentValidationPass.cs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Razor.PooledObjects;
910
using Microsoft.AspNetCore.Razor.Threading;
1011
using Microsoft.CodeAnalysis.Razor.Logging;
1112
using Microsoft.CodeAnalysis.Text;
@@ -29,28 +30,37 @@ public Task<bool> IsValidAsync(FormattingContext context, ImmutableArray<TextCha
2930
// Looks like we removed some non-whitespace content as part of formatting. Oops.
3031
// Discard this formatting result.
3132

32-
_logger.LogWarning($"{SR.Format_operation_changed_nonwhitespace}");
33-
34-
foreach (var change in changes)
35-
{
36-
if (change.NewText?.Any(c => !char.IsWhiteSpace(c)) ?? false)
37-
{
38-
_logger.LogWarning($"{SR.FormatEdit_at_adds(text.GetLinePositionSpan(change.Span), change.NewText)}");
39-
}
40-
else if (text.TryGetFirstNonWhitespaceOffset(change.Span, out _))
41-
{
42-
_logger.LogWarning($"{SR.FormatEdit_at_deletes(text.GetLinePositionSpan(change.Span), text.ToString(change.Span))}");
43-
}
44-
}
33+
var message = GetLogMessage(changes, text);
34+
_logger.LogError(message);
4535

4636
if (DebugAssertsEnabled)
4737
{
48-
Debug.Fail("A formatting result was rejected because it was going to change non-whitespace content in the document.");
38+
Debug.Fail(message);
4939
}
5040

5141
return SpecializedTasks.False;
5242
}
5343

5444
return SpecializedTasks.True;
5545
}
46+
47+
private static string GetLogMessage(ImmutableArray<TextChange> changes, SourceText text)
48+
{
49+
using var _1 = StringBuilderPool.GetPooledObject(out var builder);
50+
builder.AppendLine(SR.Format_operation_changed_nonwhitespace);
51+
52+
foreach (var change in changes)
53+
{
54+
if (change.NewText?.Any(c => !char.IsWhiteSpace(c)) ?? false)
55+
{
56+
builder.AppendLine(SR.FormatEdit_at_adds(text.GetLinePositionSpan(change.Span), change.NewText));
57+
}
58+
else if (text.TryGetFirstNonWhitespaceOffset(change.Span, out _))
59+
{
60+
builder.AppendLine(SR.FormatEdit_at_deletes(text.GetLinePositionSpan(change.Span), text.ToString(change.Span)));
61+
}
62+
}
63+
64+
return builder.ToString();
65+
}
5666
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/FormattingDiagnosticValidationPass.cs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Threading;
1010
using System.Threading.Tasks;
1111
using Microsoft.AspNetCore.Razor.Language;
12+
using Microsoft.AspNetCore.Razor.PooledObjects;
1213
using Microsoft.CodeAnalysis.Razor.Logging;
1314
using Microsoft.CodeAnalysis.Text;
1415

@@ -37,22 +38,12 @@ public async Task<bool> IsValidAsync(FormattingContext context, ImmutableArray<T
3738
// at all possible). Also worth noting the order has to be maintained in that case.
3839
if (!originalDiagnostics.SequenceEqual(changedDiagnostics, LocationIgnoringDiagnosticComparer.Instance))
3940
{
40-
_logger.LogWarning($"{SR.Format_operation_changed_diagnostics}");
41-
_logger.LogWarning($"{SR.Diagnostics_before}");
42-
foreach (var diagnostic in originalDiagnostics)
43-
{
44-
_logger.LogWarning($"{diagnostic}");
45-
}
46-
47-
_logger.LogWarning($"{SR.Diagnostics_after}");
48-
foreach (var diagnostic in changedDiagnostics)
49-
{
50-
_logger.LogWarning($"{diagnostic}");
51-
}
41+
var message = GetLogMessage(originalDiagnostics, changedDiagnostics);
42+
_logger.LogError(message);
5243

5344
if (DebugAssertsEnabled)
5445
{
55-
Debug.Fail("A formatting result was rejected because the formatted text produced different diagnostics compared to the original text.");
46+
Debug.Fail(message);
5647
}
5748

5849
return false;
@@ -61,6 +52,26 @@ public async Task<bool> IsValidAsync(FormattingContext context, ImmutableArray<T
6152
return true;
6253
}
6354

55+
private static string GetLogMessage(ImmutableArray<RazorDiagnostic> originalDiagnostics, ImmutableArray<RazorDiagnostic> changedDiagnostics)
56+
{
57+
using var _ = StringBuilderPool.GetPooledObject(out var builder);
58+
59+
builder.AppendLine(SR.Format_operation_changed_diagnostics);
60+
builder.AppendLine(SR.Diagnostics_before);
61+
foreach (var diagnostic in originalDiagnostics)
62+
{
63+
builder.AppendLine(diagnostic.ToString());
64+
}
65+
66+
builder.AppendLine(SR.Diagnostics_after);
67+
foreach (var diagnostic in changedDiagnostics)
68+
{
69+
builder.AppendLine(diagnostic.ToString());
70+
}
71+
72+
return builder.ToString();
73+
}
74+
6475
private class LocationIgnoringDiagnosticComparer : IEqualityComparer<RazorDiagnostic>
6576
{
6677
public static IEqualityComparer<RazorDiagnostic> Instance = new LocationIgnoringDiagnosticComparer();

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Logging/RemoteLoggerFactory.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,20 @@ internal sealed class RemoteLoggerFactory : ILoggerFactory
1313
{
1414
private ILoggerFactory _targetLoggerFactory = EmptyLoggerFactory.Instance;
1515

16-
internal void SetTargetLoggerFactory(ILoggerFactory loggerFactory)
16+
/// <summary>
17+
/// Sets the logger factory to use in OOP
18+
/// </summary>
19+
/// <returns>True if the factory was set</returns>
20+
internal bool SetTargetLoggerFactory(ILoggerFactory loggerFactory)
1721
{
1822
// We only set the target logger factory if the current factory is empty.
1923
if (_targetLoggerFactory is EmptyLoggerFactory)
2024
{
2125
_targetLoggerFactory = loggerFactory;
26+
return true;
2227
}
28+
29+
return false;
2330
}
2431

2532
public void AddLoggerProvider(ILoggerProvider provider)

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/RazorBrokeredServiceBase.FactoryBase`1.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ protected virtual async Task<object> CreateInternalAsync(
9494
// Note that this means that the first non-empty ILoggerFactory that we use
9595
// will be used for MEF component logging for the lifetime of all services.
9696
var remoteLoggerFactory = exportProvider.GetExportedValue<RemoteLoggerFactory>();
97-
remoteLoggerFactory.SetTargetLoggerFactory(targetLoggerFactory);
97+
var didSetLoggerFactory = remoteLoggerFactory.SetTargetLoggerFactory(targetLoggerFactory);
9898

9999
// In proc services don't use any service hub infra
100100
if (stream is null)
@@ -103,6 +103,13 @@ protected virtual async Task<object> CreateInternalAsync(
103103
return CreateService(in inProcArgs);
104104
}
105105

106+
// At this point, we know we're in a remote scenario where we're on the end of a service hub connection, so we want
107+
// logged errors to be thrown, so they're bubbled up to the client.
108+
if (didSetLoggerFactory)
109+
{
110+
remoteLoggerFactory.AddLoggerProvider(new ThrowingErrorLoggerProvider());
111+
}
112+
106113
var serverConnection = CreateServerConnection(stream, traceSource);
107114

108115
var args = new ServiceArgs(serviceBroker.AssumeNotNull(), exportProvider, targetLoggerFactory, workspaceProvider, serverConnection, brokeredServiceData?.Interceptor);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using Microsoft.CodeAnalysis.Razor.Logging;
6+
7+
namespace Microsoft.CodeAnalysis.Remote.Razor;
8+
9+
/// <summary>
10+
/// An error logger provider that provides a logger that simple throws an exception for any LogError call.
11+
/// </summary>
12+
internal class ThrowingErrorLoggerProvider : ILoggerProvider
13+
{
14+
public ILogger CreateLogger(string categoryName)
15+
{
16+
return Logger.Instance;
17+
}
18+
19+
private class Logger : ILogger
20+
{
21+
public static readonly Logger Instance = new();
22+
23+
public bool IsEnabled(LogLevel logLevel)
24+
{
25+
return logLevel == LogLevel.Error;
26+
}
27+
28+
public void Log(LogLevel logLevel, string message, Exception? exception)
29+
{
30+
if (logLevel != LogLevel.Error)
31+
{
32+
return;
33+
}
34+
35+
if (exception is not null)
36+
{
37+
throw exception;
38+
}
39+
40+
throw new InvalidOperationException(message);
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)