Add flow logger to help with console output parallelism

This commit is contained in:
Nate McMaster 2017-07-13 15:47:52 -07:00
parent cd11f71428
commit d3ab458c6c
5 changed files with 226 additions and 1 deletions

View File

@ -29,6 +29,7 @@
<PropertyGroup>
<!-- If there are duplicate properties, the properties which are defined later in the order would override the earlier ones -->
<RepositoryBuildArguments>$(RepositoryBuildArguments) /p:BuildNumber=$(BuildNumber) /p:Configuration=$(Configuration) /p:CommitHash=$(CommitHash)</RepositoryBuildArguments>
<RepositoryBuildArguments>$(RepositoryBuildArguments) /noconsolelogger '/l:RepoTasks.FlowLogger,$(MSBuildThisFileDirectory)tasks\bin\publish\RepoTasks.dll;Summary;FlowId=$(RepositoryToBuild)'</RepositoryBuildArguments>
<BuildArguments>$(_RepositoryBuildTargets) $(RepositoryBuildArguments)</BuildArguments>
<RepositoryArtifactsRoot>$(BuildRepositoryRoot)artifacts</RepositoryArtifactsRoot>
@ -91,4 +92,4 @@
<Exec Command="$(PinVersionArgs)" />
</Target>
</Project>
</Project>

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
namespace RepoTasks
{
internal class DefaultPrefixMessageWriter : IWriter
{
private readonly string _flowId;
public DefaultPrefixMessageWriter(WriteHandler write, string flowId)
{
_flowId = flowId;
var prefix = $"{_flowId,-22}| ";
WriteHandler = msg => write(prefix + msg);
}
public WriteHandler WriteHandler { get; }
public void OnBuildStarted(BuildStartedEventArgs e)
{
WriteHandler(e.Message + Environment.NewLine);
}
public void OnBuildFinished(BuildFinishedEventArgs e)
{
}
}
}

View File

@ -0,0 +1,69 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
namespace RepoTasks
{
public class FlowLogger : ConsoleLogger
{
private static readonly bool IsTeamCity = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TEAMCITY_PROJECT_NAME"));
private volatile bool _initialized;
public FlowLogger()
{
}
public override void Initialize(IEventSource eventSource, int nodeCount)
{
PreInit(eventSource);
base.Initialize(eventSource, nodeCount);
}
public override void Initialize(IEventSource eventSource)
{
PreInit(eventSource);
base.Initialize(eventSource);
}
private void PreInit(IEventSource eventSource)
{
if (_initialized) return;
_initialized = true;
var _flowId = GetFlowId();
var writer = IsTeamCity
? (IWriter)new TeamCityMessageWriter(WriteHandler, _flowId)
: new DefaultPrefixMessageWriter(WriteHandler, _flowId);
WriteHandler = writer.WriteHandler;
eventSource.BuildStarted += (o, e) =>
{
writer.OnBuildStarted(e);
};
eventSource.BuildFinished += (o, e) =>
{
writer.OnBuildFinished(e);
};
}
private string GetFlowId()
{
var parameters = Parameters?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
if (parameters == null || parameters.Length == 0)
{
return null;
}
const string flowIdParamName = "FlowId=";
return parameters
.FirstOrDefault(p => p.StartsWith(flowIdParamName, StringComparison.Ordinal))
?.Substring(flowIdParamName.Length);
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
namespace RepoTasks
{
internal interface IWriter
{
WriteHandler WriteHandler { get; }
void OnBuildFinished(BuildFinishedEventArgs e);
void OnBuildStarted(BuildStartedEventArgs e);
}
}

View File

@ -0,0 +1,107 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
namespace RepoTasks
{
/// <summary>
/// See https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity
/// </summary>
internal class TeamCityMessageWriter : IWriter
{
private const string MessagePrefix = "##teamcity";
private static readonly string EOL = Environment.NewLine;
private readonly WriteHandler _write;
private readonly string _flowIdAttr;
public TeamCityMessageWriter(WriteHandler write, string flowId)
{
_write = write;
_flowIdAttr = EscapeTeamCityText(flowId);
WriteHandler = CreateMessageHandler();
}
public WriteHandler WriteHandler { get; }
public void OnBuildFinished(BuildFinishedEventArgs e)
{
_write($"##teamcity[blockOpened name='Build {_flowIdAttr}' flowId='{_flowIdAttr}']" + EOL);
}
public void OnBuildStarted(BuildStartedEventArgs e)
{
_write($"##teamcity[blockClosed name='Build {_flowIdAttr}' flowId='{_flowIdAttr}']" + EOL);
}
private WriteHandler CreateMessageHandler()
{
var format = "##teamcity[message text='{0}' flowId='" + _flowIdAttr + "']" + EOL;
return message =>
{
if (string.IsNullOrEmpty(message))
{
return;
}
if (message.StartsWith(MessagePrefix, StringComparison.Ordinal))
{
_write(message);
return;
}
_write(
string.Format(
System.Globalization.CultureInfo.InvariantCulture,
format,
EscapeTeamCityText(message)));
};
}
private static string EscapeTeamCityText(string txt)
{
if (string.IsNullOrEmpty(txt))
{
return txt;
}
var sb = new StringBuilder(txt.Length);
for (var i = 0; i < txt.Length; i++)
{
var ch = txt[i];
switch (ch)
{
case '\'':
case '|':
case '[':
case ']':
sb.Append('|').Append(ch);
break;
case '\n':
sb.Append("|n");
break;
case '\r':
sb.Append("|r");
break;
case '\u0085':
sb.Append("|x");
break;
case '\u2028':
sb.Append("|l");
break;
case '\u2029':
sb.Append("|p");
break;
default:
sb.Append(ch);
break;
}
}
return sb.ToString();
}
}
}