File: Debugging\PrintLineDebuggerWriters.cs
Web Access
Project: Microsoft.Build.Framework.csproj (Microsoft.Build.Framework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using CommonWriterType = System.Action<string, string, System.Collections.Generic.IEnumerable<string>>;

#nullable disable

namespace Microsoft.Build.Shared.Debugging;

/// <summary>
///     A collection of useful writers
/// </summary>
internal static class PrintLineDebuggerWriters
{
    public sealed class IdBasedFilesWriter
    {
        private string LogFileRoot { get; }

        public CommonWriterType Writer => (id, callsite, args) =>
        {
            try
            {
                var file = Path.Combine(LogFileRoot, string.IsNullOrEmpty(id) ? "NoId" : id) + ".csv";
                File.AppendAllText(file, CsvFormat(string.Empty, callsite, args));
            }
            catch (Exception e)
            {
                var errorFile = Path.Combine(LogFileRoot, $"LoggingException_{Guid.NewGuid()}");
                File.AppendAllText(errorFile, $"{SimpleFormat(id, callsite, args)}\n{e.Message}\n{e.StackTrace}");
            }
        };

        public IdBasedFilesWriter(string logFileRoot)
        {
            this.LogFileRoot = logFileRoot;
            Directory.CreateDirectory(logFileRoot);
        }

        public static IdBasedFilesWriter FromArtifactLogDirectory()
        {
            return new IdBasedFilesWriter(ArtifactsLogDirectory);
        }
    }

    public sealed class CompositeWriter
    {
        private IEnumerable<CommonWriterType> Writers { get; }

        public CommonWriterType Writer => (id, callsite, args) =>
        {
            var argsArray = args as string[] ?? args.ToArray();

            foreach (var writer in Writers)
            {
                writer(id, callsite, argsArray);
            }
        };

        public CompositeWriter(IEnumerable<CommonWriterType> writers)
        {
            Writers = writers;
        }
    }

    public static CommonWriterType StdOutWriter = (id, callsite, args) => Console.WriteLine(SimpleFormat(id, callsite, args));

    private static readonly Lazy<string> _artifactsLogs = new Lazy<string>(
        () =>
        {
            var executingAssembly = typeof(PrintLineDebuggerWriters).GetAssemblyPath();

            var binPart = $"bin";

            var logIndex = executingAssembly.IndexOf(binPart, StringComparison.Ordinal);

            var artifactsPart = executingAssembly.Substring(0, logIndex);
            return logIndex < 0
                ? null
                : Path.Combine(artifactsPart, "log", "Debug");
        });

    public static string ArtifactsLogDirectory => _artifactsLogs.Value;

    public static string SimpleFormat(string id, string callsite, IEnumerable<string> args)
    {
        return $"\n{(id == null ? string.Empty : id + ": ")}{callsite}:{string.Join(";", args)}";
    }

    public static string CsvFormat(string id, string callsite, IEnumerable<string> args)
    {
        var joinedArgs = $"{EscapeCommas(callsite)},{string.Join(",", args.Select(arg => EscapeCommas(arg)))}\n";

        return string.IsNullOrEmpty(id)
            ? joinedArgs
            : $"{EscapeCommas(id)},{joinedArgs}";

        string EscapeCommas(string s)
        {
            return s.Replace(",", ";");
        }
    }
}