Skip to content

Commit f927ccc

Browse files
committed
Merged PR 725421: Add the ability to selectively send logs to the console
The idea is that a new listener is created that selectively sends logs based on a user configurable knob to an internal Logger that the console listener listens to (in addition to the standard Events source). When the (only) event in sent to the internal logger, the console listener displays that in the console 'as is'. Related work items: #2074267
1 parent a0d4231 commit f927ccc

File tree

13 files changed

+189
-0
lines changed

13 files changed

+189
-0
lines changed

Documentation/Wiki/Flags.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ This page lists flags that can be used to configure BuildXL.
132132
| LogStats | Logs key/value statistics to default stats file in the same folder as the main log file. Defaults to on. |
133133
| LogStatus | Logs build status information to a CSV file in the same folder as the main log file. Defaults to on. |
134134
| LogsToRetain | The number of previous logs to retain. |
135+
| LogToConsole | Displays the specified messages in the console. |
135136
| LogToKusto | Whether to send log events to Kusto. If enabled, a valid authentication mechanism should be available with enough permissions to write into the blob storage account where logs are piped to Kusto. Use /logToKustoBlobUri:https://{storage-account-name}/{container-name} /logToKustoIdentityId:{Identity guid} and /logToKustoTenantId:{Tenant guid} to specify the destination of the log messages. |
136137
| LowPriority | Runs the build engine and all tools at a lower priority in order to provide better responsiveness to interactive processes on the current machine. |
137138
| ManageMemoryMode | Specifies the mode to manage memory under pressure. Defaults to CancellationRam where {ShortProductName} attemps to cancel processes. EmptyWorkingSet mode will empty working set of processes instead of cancellation. Suspend mode will suspend processes to free memory. |

Public/Src/App/Bxl/Args.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,9 @@ public bool TryParse(string[] args, PathTable pathTable, out ICommandLineConfigu
342342
OptionHandlerFactory.CreateOption(
343343
"logToKustoTenantId",
344344
opt => loggingConfiguration.LogToKustoTenantId = opt.Value),
345+
OptionHandlerFactory.CreateOption(
346+
"logToConsole",
347+
opt => ParseInt32ListOption(opt, loggingConfiguration.LogEventsToConsole)),
345348
OptionHandlerFactory.CreateBoolOption(
346349
"debuggerBreakOnExit",
347350
opt => frontEndConfiguration.DebuggerBreakOnExit = opt),

Public/Src/App/Bxl/BuildXLApp.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
using Azure.Storage.Blobs;
5858
using Azure.Core.Pipeline;
5959
using Microsoft.WindowsAzure.Storage.RetryPolicies;
60+
using BuildXL.ConsoleRedirector;
6061

6162
namespace BuildXL
6263
{
@@ -1593,6 +1594,11 @@ public void ConfigureLogging(LoggingContext loggingContext)
15931594
{
15941595
ConfigureKustoLogging(loggingContext);
15951596
}
1597+
1598+
if (m_configuration.LogEventsToConsole.Count > 0)
1599+
{
1600+
ConfigureConsoleRedirection(loggingContext);
1601+
}
15961602
}
15971603
}
15981604

@@ -2006,6 +2012,18 @@ private void ConfigureKustoLogging(LoggingContext loggingContext)
20062012
});
20072013
}
20082014

2015+
private void ConfigureConsoleRedirection(LoggingContext loggingContext)
2016+
{
2017+
var consoleRedirector = new ConsoleRedirectorEventListener(
2018+
Events.Log,
2019+
m_baseTime,
2020+
m_configuration.LogEventsToConsole,
2021+
loggingContext,
2022+
m_warningManager.GetState);
2023+
2024+
AddListener(consoleRedirector);
2025+
}
2026+
20092027
/// <summary>
20102028
/// Registers generated event sources as merged event sources
20112029
///

Public/Src/App/Bxl/HelpText.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@ public static void DisplayHelp(HelpLevel helpLevel)
399399
"/dumpFailedPipsLogLimit:<number>",
400400
Strings.HelpText_DisplayHelp_DumpFailedPipsLogLimit);
401401

402+
hw.WriteOption(
403+
"/logToConsole:<event id list>",
404+
Strings.HelpText_DisplayHelp_LogToConsole,
405+
HelpLevel.Verbose);
406+
402407
#endregion
403408

404409
hw.WriteBanner(Strings.HelpText_DisplayHelp_ErrorsAndWarningsBanner);

Public/Src/App/Bxl/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,4 +1153,7 @@ Example: ad2d42d2ec5d2ca0c0b7ad65402d07c7ef40b91e</value>
11531153
<data name="App_ConfigureFileLogging_BlobListenerError" xml:space="preserve">
11541154
<value>A problem ocurred while uploading logs to Kusto: {0}</value>
11551155
</data>
1156+
<data name="HelpText_DisplayHelp_LogToConsole" xml:space="preserve">
1157+
<value>Displays the specified messages in the console.</value>
1158+
</data>
11561159
</root>

Public/Src/App/ConsoleLogger/BuildXL.ConsoleLogger.dsc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace ConsoleLogger {
1212
export const dll = BuildXLSdk.library({
1313
assemblyName: "BuildXL.ConsoleLogger",
1414
sources: globR(d`.`, "*.cs"),
15+
generateLogs: true,
1516
embeddedResources: [{resX: f`Strings.resx`}],
1617
references: [
1718
importFrom("BuildXL.Engine").Scheduler.dll,

Public/Src/App/ConsoleLogger/ConsoleEventListener.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using BuildXL.Pips;
1515
using BuildXL.Pips.Operations;
1616
using BuildXL.Utilities;
17+
using BuildXL.Utilities.Collections;
1718
using BuildXL.Utilities.Core;
1819
using BuildXL.Utilities.Instrumentation.Common;
1920
using BuildXL.Utilities.Tracing;
@@ -212,6 +213,12 @@ public ConsoleEventListener(
212213
m_notWorker = notWorker;
213214
m_optimizeForAzureDevOps = optimizeForAzureDevOps;
214215
m_statusMessageThrottler = new StatusMessageThrottler(baseTime);
216+
217+
// Only the console listener in interested in this event source. All events specified
218+
// with /logToConsole will be redirected to this event source and picked up here
219+
// This avoids the redirection to show anywhere else. Observe that this event source
220+
// is registered in addition to the specified eventSource
221+
RegisterEventSource(ConsoleLogger.ETWLogger.Log);
215222
}
216223

217224
/// <summary>
@@ -365,6 +372,16 @@ protected override void OnInformational(EventWrittenEventArgs eventData)
365372

366373
break;
367374
}
375+
// This is the case of regular log events sent to the console honoring a user configurable option
376+
case (int)ConsoleRedirector.Tracing.LogEventId.LogToConsole:
377+
{
378+
// The message is already fully formatted, so just put it back together
379+
object[] args = eventData.Payload == null ? CollectionUtilities.EmptyArray<object>() : eventData.Payload.ToArray();
380+
string finalMessage = string.Format(CultureInfo.CurrentCulture, eventData.Message, args);
381+
382+
Output(MessageLevel.Info, finalMessage);
383+
break;
384+
}
368385

369386
default:
370387
{
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Diagnostics.ContractsLight;
8+
using System.Diagnostics.Tracing;
9+
using BuildXL.Utilities.Instrumentation.Common;
10+
using BuildXL.Utilities.Tracing;
11+
12+
namespace BuildXL
13+
{
14+
/// <summary>
15+
/// Listens to an event source and selectively send events to the console
16+
/// </summary>
17+
public sealed class ConsoleRedirectorEventListener : FormattingEventListener
18+
{
19+
private readonly HashSet<int> m_eventIdsToMap;
20+
private readonly LoggingContext m_loggingContext;
21+
22+
/// <nodoc />
23+
public ConsoleRedirectorEventListener(
24+
Events eventSource,
25+
DateTime baseTime,
26+
IEnumerable<int> eventsToRedirect,
27+
LoggingContext loggingContext,
28+
WarningMapper warningMapper)
29+
: base(eventSource, baseTime, warningMapper: warningMapper, level: EventLevel.Verbose, captureAllDiagnosticMessages: false, timeDisplay: TimeDisplay.Seconds)
30+
{
31+
Contract.Requires(eventsToRedirect != null);
32+
m_eventIdsToMap = new HashSet<int>(eventsToRedirect);
33+
m_loggingContext = loggingContext;
34+
}
35+
36+
/// <inheritdoc />
37+
protected override void Output(EventLevel level, EventWrittenEventArgs eventData, string text, bool doNotTranslatePaths = false)
38+
{
39+
if (!m_eventIdsToMap.Contains(eventData.EventId))
40+
{
41+
return;
42+
}
43+
44+
// If the event should be redirected, log it via this special log message
45+
ConsoleRedirector.Tracing.Logger.Log.LogToConsole(m_loggingContext, text);
46+
}
47+
}
48+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using BuildXL.Utilities.Instrumentation.Common;
5+
6+
#pragma warning disable 1591
7+
#pragma warning disable CA1823 // Unused field
8+
#nullable enable
9+
10+
namespace BuildXL.ConsoleRedirector.Tracing
11+
{
12+
/// <summary>
13+
/// Special logging to send standard log messages to the console. Not intended for bxl components to use directly.
14+
/// </summary>
15+
[EventKeywordsType(typeof(Keywords))]
16+
[EventTasksType(typeof(Tasks))]
17+
[LoggingDetails("ConsoleRedirector")]
18+
public abstract partial class Logger
19+
{
20+
/// <summary>
21+
/// Returns the logger instance
22+
/// </summary>
23+
public static Logger Log => m_log;
24+
25+
26+
/// <summary>
27+
/// A <see cref="ConsoleRedirectorEventListener"/> will selectively log standard log events via this method
28+
/// so the ConsoleEventListener can pick them up
29+
/// </summary>
30+
[GeneratedEvent(
31+
(ushort)LogEventId.LogToConsole,
32+
EventGenerators = BuildXL.Tracing.EventGenerators.LocalOnly,
33+
EventLevel = Level.Informational,
34+
EventTask = (ushort)Tasks.Unclassified,
35+
Message = "{text}")]
36+
public abstract void LogToConsole(LoggingContext context, string text);
37+
}
38+
}
39+
40+
#pragma warning restore CA1823 // Unused field
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace BuildXL.ConsoleRedirector.Tracing
5+
{
6+
// disable warning regarding 'missing XML comments on public API'. We don't need docs for these values
7+
#pragma warning disable 1591
8+
9+
/// <summary>
10+
/// Defines event IDs corresponding to events in <see cref="Logger" />
11+
/// </summary>
12+
public enum LogEventId
13+
{
14+
LogToConsole = 15006
15+
}
16+
}

Public/Src/App/UnitTests/Bxl/ConsoleEventListenerTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Collections.Generic;
6+
using System.Reflection;
57
using System.Threading;
68
using BuildXL;
79
using BuildXL.ToolSupport;
@@ -237,5 +239,27 @@ public void TestPercentDone()
237239
XAssert.AreEqual("99.95% ", ConsoleEventListener.ComputePercentDone(1000, 1000, 50, 100));
238240
XAssert.AreEqual("99.95% ", ConsoleEventListener.ComputePercentDone(9999, 10000, 50, 100));
239241
}
242+
243+
[Fact]
244+
public void TestEventRedirection(bool suppressWarning)
245+
{
246+
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
247+
CancellationToken cancellationToken = cancellationTokenSource.Token;
248+
var loggingContext = BuildXLTestBase.CreateLoggingContextForTest();
249+
250+
using (var console = new MockConsole())
251+
// Let's create and register a redirector listener to send an arbitrary message to the console
252+
using (var redirectorListener = new ConsoleRedirectorEventListener(Events.Log, DateTime.UtcNow, new List<int> { (int)TestEvents.EventId.DiagnosticEvent}, loggingContext, warningMapper: null))
253+
using (var listener = new ConsoleEventListener(Events.Log, console, DateTime.UtcNow, false, cancellationToken))
254+
{
255+
listener.RegisterEventSource(TestEvents.Log);
256+
redirectorListener.RegisterEventSource(TestEvents.Log);
257+
258+
var message = "I'm an event that should be redirected to the console";
259+
TestEvents.Log.DiagnosticEvent(message);
260+
261+
console.ValidateCall(MessageLevel.Info, message);
262+
}
263+
}
240264
}
241265
}

Public/Src/Utilities/Configuration/ILoggingConfiguration.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,5 +464,10 @@ public interface ILoggingConfiguration : IWarningHandling
464464
/// Default value points to the Bxl-owned identity 6e0959cf-a9ba-4988-bbf1-7facd9deda51
465465
/// </remarks>
466466
public string LogToKustoIdentityId { get; }
467+
468+
/// <summary>
469+
/// Collection of log event ids that should be sent to the console
470+
/// </summary>
471+
public IReadOnlyList<int> LogEventsToConsole { get; }
467472
}
468473
}

Public/Src/Utilities/Configuration/Mutable/LoggingConfiguration.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public LoggingConfiguration()
6666

6767
PerfCollectorFrequencyMs = 5_000;
6868
LogToKusto = false;
69+
LogEventsToConsole = new List<int>();
6970
}
7071

7172
/// <nodoc />
@@ -170,6 +171,7 @@ public LoggingConfiguration(ILoggingConfiguration template, PathRemapper pathRem
170171
LogToKustoBlobUri = template.LogToKustoBlobUri;
171172
LogToKustoIdentityId = template.LogToKustoIdentityId;
172173
LogToKustoTenantId = template.LogToKustoTenantId;
174+
LogEventsToConsole = new List<int>(template.LogEventsToConsole);
173175
}
174176

175177
/// <inheritdoc />
@@ -433,5 +435,11 @@ public AbsolutePath LogsRootDirectory(PathTable table)
433435

434436
/// <inheritdoc/>
435437
public string LogToKustoIdentityId { get; set; }
438+
439+
/// <nodoc/>
440+
public List<int> LogEventsToConsole { get; set; }
441+
442+
/// <inheritdoc/>
443+
IReadOnlyList<int> ILoggingConfiguration.LogEventsToConsole => LogEventsToConsole;
436444
}
437445
}

0 commit comments

Comments
 (0)