This repository has been archived by the owner on Jan 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 171
/
Copy pathLogging.cs
253 lines (217 loc) · 10.4 KB
/
Logging.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Quantum.QsCompiler.CompilationBuilder;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.Quantum.QsCompiler.Diagnostics
{
public interface ILogger
{
void Log(ErrorCode item, IEnumerable<string> args, string? source = null, LSP.Range? range = null);
void Log(WarningCode item, IEnumerable<string> args, string? source = null, LSP.Range? range = null);
void Log(InformationCode item, IEnumerable<string> args, string? source = null, LSP.Range? range = null, params string[] messageParam);
void Log(params Diagnostic[] messages);
void Log(Exception ex);
}
public abstract class LogTracker : ILogger
{
public DiagnosticSeverity Verbosity { get; set; }
public int NrErrorsLogged { get; private set; }
public int NrWarningsLogged { get; private set; }
public int NrExceptionsLogged { get; private set; }
private readonly int lineNrOffset;
private readonly ImmutableArray<int> noWarn;
public LogTracker(
DiagnosticSeverity verbosity = DiagnosticSeverity.Warning,
IEnumerable<int>? noWarn = null,
int lineNrOffset = 0)
{
this.Verbosity = verbosity;
this.NrErrorsLogged = 0;
this.NrWarningsLogged = 0;
this.NrExceptionsLogged = 0;
this.lineNrOffset = lineNrOffset;
this.noWarn = noWarn?.ToImmutableArray() ?? ImmutableArray<int>.Empty;
}
// methods that need to or can be specified by a deriving class
/// <summary>
/// Called whenever a diagnostic is logged after the diagnostic has been properly processed.
/// </summary>
protected internal abstract void Print(Diagnostic msg);
/// <summary>
/// Called whenever an exception is logged after the exception has been properly tracked.
/// Prints the given exception as Hint if the logger verbosity is sufficiently high.
/// </summary>
protected internal virtual void OnException(Exception ex)
{
var verbosity = this.Verbosity;
this.Verbosity = DiagnosticSeverity.Hint;
this.Output(ex == null ? null : new Diagnostic
{
Severity = DiagnosticSeverity.Hint,
Message = $"{Environment.NewLine}{ex}{Environment.NewLine}",
});
this.Verbosity = verbosity;
}
// NB: Calling the LSP.Range constructor results in an object with
// non-nullable fields set to null values, confusing other places
// where we use nullable reference type metadata. To address this,
// we explicitly construct an empty range that runs from 0:0 to 0:0
// that we can use when there is no reasonable range to provide.
private static readonly LSP.Range EmptyRange = new LSP.Range { Start = new Position(0, 0), End = new Position(0, 0) };
// routines for convenience
/// <summary>
/// Logs a diagnostic message based on the given error code,
/// with the given source as the file for which the error occurred.
/// </summary>
public void Log(ErrorCode code, IEnumerable<string> args, string? source = null, LSP.Range? range = null) =>
this.Log(new Diagnostic
{
Severity = DiagnosticSeverity.Error,
Code = Errors.Code(code),
Source = source,
Message = DiagnosticItem.Message(code, args ?? Enumerable.Empty<string>()),
Range = range ?? EmptyRange,
});
/// <summary>
/// Logs a a diagnostic message based on the given warning code,
/// with the given source as the file for which the error occurred.
/// </summary>
public void Log(WarningCode code, IEnumerable<string> args, string? source = null, LSP.Range? range = null) =>
this.Log(new Diagnostic
{
Severity = DiagnosticSeverity.Warning,
Code = Warnings.Code(code),
Source = source,
Message = DiagnosticItem.Message(code, args ?? Enumerable.Empty<string>()),
Range = range ?? EmptyRange,
});
/// <summary>
/// Generates a Diagnostic message based on the given information code,
/// with any message parameters appended on a new line to the message defined by the information code.
/// The given source is listed as the file for which the error occurred.
/// </summary>
public void Log(InformationCode code, IEnumerable<string> args, string? source = null, LSP.Range? range = null, params string[] messageParam) =>
this.Log(new Diagnostic
{
Severity = DiagnosticSeverity.Information,
Code = null, // do not show a code for infos
Source = source,
Message = $"{DiagnosticItem.Message(code, args ?? Enumerable.Empty<string>())}{Environment.NewLine}{string.Join(Environment.NewLine, messageParam)}",
Range = range ?? EmptyRange,
});
/// <summary>
/// Logs the given diagnostic messages.
/// Ignores any parameter that is null.
/// </summary>
public void Log(params Diagnostic[] messages)
{
if (messages == null)
{
return;
}
foreach (var m in messages)
{
if (m == null)
{
continue;
}
this.Log(m);
}
}
// core routines which track logged diagnostics
/// <summary>
/// Calls Print on the given diagnostic if the verbosity is sufficiently high.
/// Does nothing if the given diagnostic is null.
/// </summary>
private void Output(Diagnostic? msg)
{
if (msg?.Severity <= this.Verbosity)
{
this.Print(msg);
}
}
/// <summary>
/// Increases the error or warning counter if appropriate, and
/// prints the given diagnostic ff the logger verbosity is sufficiently high.
/// Before printing, the line numbers are shifted by the offset specified upon initialization.
/// Returns without doing anything if the given diagnostic is a warning that is to be ignored.
/// </summary>
public void Log(Diagnostic m)
{
string? strcode = m.Code?.Second; // Getting the string for the diagnostics code object.
if (m.Severity == DiagnosticSeverity.Warning &&
strcode != null &&
CompilationBuilder.Diagnostics.TryGetCode(strcode, out int code)
&& this.noWarn.Contains(code))
{
return;
}
if (m.Severity == DiagnosticSeverity.Error)
{
++this.NrErrorsLogged;
}
if (m.Severity == DiagnosticSeverity.Warning)
{
++this.NrWarningsLogged;
}
// We only want to print line number offsets if at least one of the
// start and end ranges are not both empty.
var msg = m.Range == EmptyRange ? m : m.WithLineNumOffset(this.lineNrOffset);
this.Output(msg);
}
/// <summary>
/// Increases the exception counter and calls OnException with the given exception.
/// </summary>
public void Log(Exception ex)
{
++this.NrExceptionsLogged;
this.OnException(ex);
}
}
public static class Formatting
{
public static IEnumerable<string> Indent(params string[] items) =>
items?.Select(msg => $" {msg}") ?? Enumerable.Empty<string>();
/// <summary>
/// Returns a string that contains all information about the given diagnostic in human readable format.
/// The string contains one-based position information if the range information is not null,
/// assuming the given position information is zero-based.
/// </summary>
public static string HumanReadableFormat(Diagnostic msg)
{
var codeStr = msg.Code == null ? string.Empty : $" {msg.Code}";
var (startLine, startChar) = (msg.Range?.Start?.Line + 1 ?? 0, msg.Range?.Start?.Character + 1 ?? 0);
var source = msg.Source == null ? string.Empty : $"File: {msg.Source} \n";
var position = msg.Range == null ? string.Empty : $"Position: [ln {startLine}, cn {startChar}] \n";
var message = $"{source}{position}{msg.Message ?? "no details are available"}";
return
msg.Severity == DiagnosticSeverity.Error ? $"\nError{codeStr}: \n{message}" :
msg.Severity == DiagnosticSeverity.Warning ? $"\nWarning{codeStr}: \n{message}" :
msg.Severity == DiagnosticSeverity.Information ? $"\nInformation{codeStr}: \n{message}" :
string.IsNullOrWhiteSpace(codeStr) ? $"\n{message}" : $"\n[{codeStr.Trim()}] {message}";
}
/// <summary>
/// Returns a string that contains all information about the given diagnostic
/// in a format that is detected and processed as a diagnostic by VS and VS Code.
/// The string contains one-based position information if the range information is not null,
/// assuming the given position information is zero-based.
/// </summary>
public static string MsBuildFormat(Diagnostic msg)
{
var codeStr = msg.Code == null || msg.Code?.Second == null ? string.Empty : $" {msg.Code?.Second}";
var (startLine, startChar) = (msg.Range?.Start?.Line + 1 ?? 0, msg.Range?.Start?.Character + 1 ?? 0);
var level =
msg.Severity == DiagnosticSeverity.Error ? "error" :
msg.Severity == DiagnosticSeverity.Warning ? "warning" :
"info";
var source = msg.Source ?? string.Empty;
var position = msg.Range == null ? string.Empty : $"({startLine},{startChar})";
return $"{source}{position}: {level}{codeStr}: {msg.Message}";
}
}
}