diff --git a/source/Nuke.Common.Tests/Execution/LogExtensionTest.cs b/source/Nuke.Common.Tests/Execution/LogExtensionTest.cs new file mode 100644 index 000000000..466ce7566 --- /dev/null +++ b/source/Nuke.Common.Tests/Execution/LogExtensionTest.cs @@ -0,0 +1,102 @@ +// Copyright 2021 Maintainers of NUKE. +// Distributed under the MIT License. +// https://github.com/nuke-build/nuke/blob/master/LICENSE + +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Nuke.Common.Execution; +using Serilog; +using Serilog.Core; +using Serilog.Events; +using Xunit; + +namespace Nuke.Common.Tests.Execution +{ + public class LogExtensionTest + { + //example: replace console logging template + [LogExtension(ConsoleTemplate="My Template: {Message:l}{NewLine}")] + private class TestBuildWithConsoleTemplate : NukeBuild + { + Target Foo => _ => _.Executes(() =>{ Log.Information("hello"); }); + } + + //example: modify (or replace) built-in logging configuration + [CustomLogConfiguration] + private class TestBuildAppendLogSink : NukeBuild + { + Target Foo => _ => _.Executes(() =>{ Log.Information("hello"); }); + } + + private class CustomLogConfiguration : LogExtensionAttribute + { + public override LoggerConfiguration Configure(LoggerConfiguration configuration, NukeBuild build) + { + //append custom logging sink + return configuration.WriteTo.Sink(CustomLogSinkInstance, LogEventLevel.Debug); + } + } + + private static CustomLogSink CustomLogSinkInstance = new CustomLogSink(); + private class CustomLogSink : ILogEventSink, IDisposable + { + public readonly List LogEvents = new List(); + + public void Emit(LogEvent logEvent) + { + LogEvents.Add(logEvent); + } + + public void Dispose() + { + LogEvents.Clear(); + } + } + + [Fact] + public void CanAppendCustomLogSink() + { + var build = new TestBuildAppendLogSink(); + Logging.Configure(build); + Log.Information("CanAppendCustomLogSink"); + + var lastMessage = CustomLogSinkInstance.LogEvents.Last(); + lastMessage.MessageTemplate.Text.Should().Be("CanAppendCustomLogSink"); + + Log.CloseAndFlush(); + } + + private class TestBuild : NukeBuild + { + Target Foo => _ => _.Executes(() =>{ Log.Information("hello"); }); + } + + [Fact] + public void CheckConsoleTemplates() + { + Logging.GetConsoleTemplate(null).Should().Be(Host.DefaultOutputTemplate); + Logging.GetConsoleTemplate(new TestBuild()).Should().Be( NukeBuild.Host.OutputTemplate ); + Logging.GetConsoleTemplate(new TestBuildWithConsoleTemplate()).Should().Be( "My Template: {Message:l}{NewLine}" ); + } + + [Fact] + public void LoggingWithBareBuild() + { + Logging.Configure(new TestBuild()); + Log.Error("LoggingWithBareBuild"); + Log.Logger.NotNull(); + Log.CloseAndFlush(); + } + + [Fact] + public void LoggingWithNullBuild() + { + Logging.Configure(null); + Log.Error("LoggingWithNullBuild"); + Log.Logger.NotNull(); + Log.CloseAndFlush(); + } + } +} diff --git a/source/Nuke.Common/Execution/LogExtensionAttribute.cs b/source/Nuke.Common/Execution/LogExtensionAttribute.cs new file mode 100644 index 000000000..224677e2a --- /dev/null +++ b/source/Nuke.Common/Execution/LogExtensionAttribute.cs @@ -0,0 +1,30 @@ +// Copyright 2021 Maintainers of NUKE. +// Distributed under the MIT License. +// https://github.com/nuke-build/nuke/blob/master/LICENSE + +using System; +using JetBrains.Annotations; +using Serilog; + +namespace Nuke.Common.Execution +{ + [PublicAPI] + public interface ILogExtension + { + float Priority { get; } + string ConsoleTemplate {get;} + LoggerConfiguration Configure(LoggerConfiguration configuration, NukeBuild build); + } + + [PublicAPI] + [AttributeUsage(AttributeTargets.Class)] + public class LogExtensionAttribute : Attribute, ILogExtension + { + public virtual float Priority {get; set;} + public string ConsoleTemplate {get; set;} + public virtual LoggerConfiguration Configure(LoggerConfiguration configuration, [CanBeNull] NukeBuild build) + { + return configuration; + } + } +} diff --git a/source/Nuke.Common/Execution/Logging.cs b/source/Nuke.Common/Execution/Logging.cs index 32abf67f2..c248b168c 100644 --- a/source/Nuke.Common/Execution/Logging.cs +++ b/source/Nuke.Common/Execution/Logging.cs @@ -48,15 +48,22 @@ public static void Configure(NukeBuild build = null) if (build != null) DeleteOldLogFiles(); - Log.Logger = new LoggerConfiguration() + var config = new LoggerConfiguration() .Enrich.With() .ConfigureHost(build) .ConfigureConsole(build) .ConfigureInMemory(build) .ConfigureFiles(build) .ConfigureLevel() - .ConfigureFilter() - .CreateLogger(); + .ConfigureFilter(); + + if (build != null) + { + foreach( var extension in build.LogExtensions ) + config = extension.Configure(config,build); + } + + Log.Logger = config.CreateLogger(); } public static LoggerConfiguration ConfigureLevel(this LoggerConfiguration configuration) @@ -71,14 +78,26 @@ public static LoggerConfiguration ConfigureFilter(this LoggerConfiguration confi public static LoggerConfiguration ConfigureConsole(this LoggerConfiguration configuration, [CanBeNull] NukeBuild build) { + return configuration .WriteTo.Console( - outputTemplate: build != null ? NukeBuild.Host.OutputTemplate : Host.DefaultOutputTemplate, + outputTemplate: GetConsoleTemplate(build), theme: (ConsoleTheme)(build != null ? NukeBuild.Host.Theme : Host.DefaultTheme), applyThemeToRedirectedOutput: true, levelSwitch: LevelSwitch); } + public static string GetConsoleTemplate([CanBeNull] NukeBuild build) + { + if (build == null) + return Host.DefaultOutputTemplate; + + var extensionTemplate = build.LogExtensions + .Select(r => r.ConsoleTemplate) + .FirstOrDefault(r => !string.IsNullOrWhiteSpace(r)); + return extensionTemplate ?? NukeBuild.Host.OutputTemplate; + } + public static LoggerConfiguration ConfigureHost(this LoggerConfiguration configuration, [CanBeNull] NukeBuild build) { if (build == null) @@ -212,7 +231,7 @@ public void Dispose() } } - internal class TargetLogEventEnricher : ILogEventEnricher + public class TargetLogEventEnricher : ILogEventEnricher { private static LogEventProperty s_defaultProperty = new LogEventProperty("Target", new ScalarValue("")); diff --git a/source/Nuke.Common/NukeBuild.Events.cs b/source/Nuke.Common/NukeBuild.Events.cs index 4c7f73d78..2732e5140 100644 --- a/source/Nuke.Common/NukeBuild.Events.cs +++ b/source/Nuke.Common/NukeBuild.Events.cs @@ -18,6 +18,7 @@ namespace Nuke.Common public abstract partial class NukeBuild { internal List BuildExtensions { get; } + internal List LogExtensions { get; } protected NukeBuild() { @@ -25,6 +26,10 @@ protected NukeBuild() .GetCustomAttributes() .Cast() .OrderByDescending(x => x.Priority).ToList(); + LogExtensions ??= GetType() + .GetCustomAttributes() + .Cast() + .OrderByDescending(x => x.Priority).ToList(); } internal void ExecuteExtension(Expression> action)