Skip to content

Commit f057254

Browse files
committed
more efficient state processing
1 parent 72276d3 commit f057254

File tree

1 file changed

+144
-126
lines changed

1 file changed

+144
-126
lines changed

src/Microsoft.Extensions.Logging.MSBuild/MSBuildLogger.cs

Lines changed: 144 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33
//
44

5+
using System.Diagnostics;
56
using Microsoft.Build.Framework;
67
using Microsoft.Build.Utilities;
78
using Microsoft.NET.StringTools;
@@ -95,146 +96,163 @@ private static MSBuildMessageParameters FormatMessage<TState>(string category, T
9596
var formatted = formatter(state, exception);
9697
builder.Append(formatted);
9798

98-
if (scopeProvider is not null)
99-
{
100-
// state will be a FormattedLogValues instance
101-
// scope will be our dictionary thing we need to probe into
102-
scopeProvider.ForEachScope((scope, state) =>
103-
{
104-
var stateItems = (state as IReadOnlyList<KeyValuePair<string, object?>>)!;
105-
string originalFormat = null!;
99+
// any unprocessed state items will be appended to the message after scope processing
100+
var unprocessedKeyValues = ProcessState(state, ref message, out string? originalFormat);
106101

107-
foreach (var kvp in stateItems)
108-
{
109-
switch (kvp.Key)
110-
{
111-
case "{OriginalFormat}":
112-
// If the key is {OriginalFormat}, we will use it to set the originalFormat variable.
113-
// This is used to avoid appending the same key again in the message.
114-
if (kvp.Value is string format)
115-
{
116-
originalFormat = format;
117-
}
118-
continue;
119-
case "Subcategory":
120-
message.subcategory = kvp.Value as string;
121-
continue;
122-
case "Code":
123-
message.code = kvp.Value as string;
124-
continue;
125-
case "HelpKeyword":
126-
message.helpKeyword = kvp.Value as string;
127-
continue;
128-
case "HelpLink":
129-
message.helpLink = kvp.Value as string;
130-
continue;
131-
case "File":
132-
message.file = kvp.Value as string;
133-
continue;
134-
case "LineNumber":
135-
if (kvp.Value is int lineNumber)
136-
message.lineNumber = lineNumber;
137-
continue;
138-
case "ColumnNumber":
139-
if (kvp.Value is int columnNumber)
140-
message.columnNumber = columnNumber;
141-
continue;
142-
case "EndLineNumber":
143-
if (kvp.Value is int endLineNumber)
144-
message.endLineNumber = endLineNumber;
145-
continue;
146-
case "EndColumnNumber":
147-
if (kvp.Value is int endColumnNumber)
148-
message.endColumnNumber = endColumnNumber;
149-
continue;
150-
default:
151-
var wrappedKey = "{" + kvp.Key + "}";
152-
if (originalFormat.Contains(wrappedKey))
153-
{
154-
// If the key is part of the format string of the original format, we don't need to append it again.
155-
continue;
156-
}
102+
// scope will be our dictionary thing we need to probe into
103+
scopeProvider?.ForEachScope((scope, state) => ProcessScope(scope, ref message, ref originalFormat, unprocessedKeyValues), state);
157104

158-
// Otherwise, append the key and value to the message.
159-
// if MSbuild had a property bag concept on the message APIs,
160-
// we could use that instead of appending to the message.
105+
Debug.Assert(originalFormat is not null, "Original format should not be null at this point - either state or scope should have provided it.");
161106

162-
builder.Append($" {kvp.Key}={kvp.Value}");
163-
continue;
164-
}
165-
}
107+
ApplyUnprocessedItemsToMessage(unprocessedKeyValues, originalFormat, builder);
108+
109+
message.message = builder.ToString();
110+
return message;
111+
}
166112

167-
if (scope is IDictionary<string, object> dict)
113+
private static void ProcessScope(object? scope, ref MSBuildMessageParameters message, ref string? originalFormat, List<KeyValuePair<string, object?>>? unprocessedKeyValues)
114+
{
115+
if (scope is IDictionary<string, object?> dict)
116+
{
117+
foreach (var kvp in dict)
118+
{
119+
switch (kvp.Key)
168120
{
169-
foreach (var kvp in dict)
170-
{
171-
switch (kvp.Key)
121+
case "{OriginalFormat}":
122+
if (originalFormat is null && kvp.Value is string format)
172123
{
173-
// map all of the keys we decide are special and map to MSbuild message concepts
174-
case "{OriginalFormat}":
175-
continue;
176-
case "Subcategory":
177-
message.subcategory = kvp.Value as string;
178-
continue;
179-
case "Code":
180-
message.code = kvp.Value as string;
181-
continue;
182-
case "HelpKeyword":
183-
message.helpKeyword = kvp.Value as string;
184-
continue;
185-
case "HelpLink":
186-
message.helpLink = kvp.Value as string;
187-
continue;
188-
case "File":
189-
message.file = kvp.Value as string;
190-
continue;
191-
case "LineNumber":
192-
if (kvp.Value is int lineNumber)
193-
message.lineNumber = lineNumber;
194-
continue;
195-
case "ColumnNumber":
196-
if (kvp.Value is int columnNumber)
197-
message.columnNumber = columnNumber;
198-
continue;
199-
case "EndLineNumber":
200-
if (kvp.Value is int endLineNumber)
201-
message.endLineNumber = endLineNumber;
202-
continue;
203-
case "EndColumnNumber":
204-
if (kvp.Value is int endColumnNumber)
205-
message.endColumnNumber = endColumnNumber;
206-
continue;
207-
default:
208-
var wrappedKey = "{" + kvp.Key + "}";
209-
if (originalFormat.Contains(wrappedKey))
210-
{
211-
// If the key is part of the format string of the original format, we don't need to append it again.
212-
continue;
213-
}
214-
215-
// Otherwise, append the key and value to the message.
216-
// if MSbuild had a property bag concept on the message APIs,
217-
// we could use that instead of appending to the message.
218-
219-
builder.Append($" {kvp.Key}={kvp.Value}");
220-
continue;
124+
originalFormat = format;
221125
}
222-
}
126+
continue;
127+
case "Subcategory":
128+
message.subcategory = kvp.Value as string;
129+
continue;
130+
case "Code":
131+
message.code = kvp.Value as string;
132+
continue;
133+
case "HelpKeyword":
134+
message.helpKeyword = kvp.Value as string;
135+
continue;
136+
case "HelpLink":
137+
message.helpLink = kvp.Value as string;
138+
continue;
139+
case "File":
140+
message.file = kvp.Value as string;
141+
continue;
142+
case "LineNumber":
143+
if (kvp.Value is int lineNumber)
144+
message.lineNumber = lineNumber;
145+
continue;
146+
case "ColumnNumber":
147+
if (kvp.Value is int columnNumber)
148+
message.columnNumber = columnNumber;
149+
continue;
150+
case "EndLineNumber":
151+
if (kvp.Value is int endLineNumber)
152+
message.endLineNumber = endLineNumber;
153+
continue;
154+
case "EndColumnNumber":
155+
if (kvp.Value is int endColumnNumber)
156+
message.endColumnNumber = endColumnNumber;
157+
continue;
158+
default:
159+
unprocessedKeyValues ??= [];
160+
unprocessedKeyValues.Add(kvp);
161+
continue;
223162
}
224-
else if (scope is string s)
163+
}
164+
}
165+
else if (scope is string s)
166+
{
167+
unprocessedKeyValues ??= [];
168+
// If the scope is a string, we treat it as an unprocessed item
169+
unprocessedKeyValues.Add(new KeyValuePair<string, object?>("Scope", s));
170+
}
171+
}
172+
173+
private static void ApplyUnprocessedItemsToMessage(List<KeyValuePair<string, object?>>? unprocessedStateItems, string originalFormat, SpanBasedStringBuilder builder)
174+
{
175+
// foreach unprocessed item, if the format string does not contain the key, append it to the message
176+
// in key=value format using the builder
177+
if (unprocessedStateItems is not null)
178+
{
179+
foreach (var kvp in unprocessedStateItems)
180+
{
181+
var wrappedKey = "{" + kvp.Key + "}";
182+
if (!originalFormat.Contains(wrappedKey))
225183
{
226-
builder.Append($" {s}");
184+
builder.Append($" {kvp.Key}={kvp.Value}");
227185
}
228-
229-
230-
}, state);
186+
}
231187
}
188+
}
232189

233-
message.message = builder.ToString();
234-
return message;
190+
private static List<KeyValuePair<string, object?>>? ProcessState<TState>(TState state, ref MSBuildMessageParameters message, out string? originalFormat)
191+
{
192+
originalFormat = null;
193+
List<KeyValuePair<string, object?>>? unmappedStateItems = null;
194+
if (state is IReadOnlyList<KeyValuePair<string, object?>> stateItems)
195+
{
196+
foreach (var kvp in stateItems)
197+
{
198+
switch (kvp.Key)
199+
{
200+
case "{OriginalFormat}":
201+
// If the key is {OriginalFormat}, we will use it to set the originalFormat variable.
202+
// This is used to avoid appending the same key again in the message.
203+
if (kvp.Value is string format)
204+
{
205+
originalFormat = format;
206+
}
207+
continue;
208+
case "Subcategory":
209+
message.subcategory = kvp.Value as string;
210+
continue;
211+
case "Code":
212+
message.code = kvp.Value as string;
213+
continue;
214+
case "HelpKeyword":
215+
message.helpKeyword = kvp.Value as string;
216+
continue;
217+
case "HelpLink":
218+
message.helpLink = kvp.Value as string;
219+
continue;
220+
case "File":
221+
message.file = kvp.Value as string;
222+
continue;
223+
case "LineNumber":
224+
if (kvp.Value is int lineNumber)
225+
message.lineNumber = lineNumber;
226+
continue;
227+
case "ColumnNumber":
228+
if (kvp.Value is int columnNumber)
229+
message.columnNumber = columnNumber;
230+
continue;
231+
case "EndLineNumber":
232+
if (kvp.Value is int endLineNumber)
233+
message.endLineNumber = endLineNumber;
234+
continue;
235+
case "EndColumnNumber":
236+
if (kvp.Value is int endColumnNumber)
237+
message.endColumnNumber = endColumnNumber;
238+
continue;
239+
default:
240+
unmappedStateItems ??= [];
241+
unmappedStateItems.Add(kvp);
242+
continue;
243+
}
244+
}
245+
return unmappedStateItems;
246+
}
247+
else
248+
{
249+
// If the state is not a list, we just create an empty message.
250+
message = new MSBuildMessageParameters();
251+
}
252+
return null;
235253
}
236254

237-
255+
238256
/// <summary>
239257
/// A struct that maps to the parameters of the MSBuild LogX methods. We'll extract this from M.E.ILogger state/scope information so that we can be maximally compatible with the MSBuild logging system.
240258
/// </summary>

0 commit comments

Comments
 (0)