File: Commands\Test\MTP\Logger.cs
Web Access
Project: src\src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;

namespace Microsoft.DotNet.Cli.Commands.Test;

internal static class Logger
{
    [InterpolatedStringHandler]
    internal readonly struct LoggerInterpolatedStringHandler
    {
        /// <summary>The handler we use to perform the formatting.</summary>
        private readonly StringBuilder.AppendInterpolatedStringHandler _stringBuilderHandler;
        private readonly StringBuilder? _stringBuilder;

        public LoggerInterpolatedStringHandler(int literalLength, int formattedCount, out bool shouldAppend)
        {
            shouldAppend = Logger.TraceEnabled;
            if (shouldAppend)
            {
                _stringBuilder = new StringBuilder();
                _stringBuilderHandler = new(literalLength, formattedCount, _stringBuilder);
            }
        }

        public override string ToString()
            => _stringBuilder?.ToString() ?? string.Empty;

        public void AppendLiteral(string value) => _stringBuilderHandler!.AppendLiteral(value);

        public void AppendFormatted<T>(T value) => _stringBuilderHandler.AppendFormatted<T>(value);

        public void AppendFormatted<T>(T value, string? format) => _stringBuilderHandler.AppendFormatted<T>(value, format);

        public void AppendFormatted<T>(T value, int alignment) => _stringBuilderHandler.AppendFormatted<T>(value, alignment);

        public void AppendFormatted<T>(T value, int alignment, string? format) => _stringBuilderHandler.AppendFormatted<T>(value, alignment, format);

        public void AppendFormatted(ReadOnlySpan<char> value) => _stringBuilderHandler.AppendFormatted(value);

        public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format);

        public void AppendFormatted(string? value) => _stringBuilderHandler.AppendFormatted(value);

        public void AppendFormatted(string? value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format);

        public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _stringBuilderHandler.AppendFormatted(value, alignment, format);
    }

    public static bool TraceEnabled { get; private set; }
    private static readonly string? _traceFilePath;
    private static readonly object _lock = new();

    static Logger()
    {
        _traceFilePath = Environment.GetEnvironmentVariable(CliConstants.TestTraceLoggingEnvVar);
        TraceEnabled = !string.IsNullOrEmpty(_traceFilePath);

        string? directoryPath = Path.GetDirectoryName(_traceFilePath);
        if (!string.IsNullOrEmpty(directoryPath))
        {
            Directory.CreateDirectory(directoryPath);
        }
    }

    /// <summary>
    /// Use this overload carefully for performance reasons.
    /// We don't want the argument to have expensive calculations.
    /// </summary>
    /// <param name="message"></param>
    public static void LogTrace(string message)
    {
        if (TraceEnabled)
        {
            LogTraceCore(message);
        }
    }

    public static void LogTrace(ref LoggerInterpolatedStringHandler handler)
    {
        if (TraceEnabled)
        {
            LogTraceCore(handler.ToString());
        }
    }

    public static void LogTrace<T>(T arg, Func<T, string> messageGetter)
    {
        if (TraceEnabled)
        {
            LogTraceCore(messageGetter(arg));
        }
    }

    private static void LogTraceCore(string message)
    {
        try
        {
            message = $"[dotnet test - {DateTimeOffset.UtcNow:MM/dd/yyyy HH:mm:ss.fff}]{message}";

            lock (_lock)
            {
                using StreamWriter logFile = File.AppendText(_traceFilePath!);
                logFile.WriteLine(message);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[dotnet test - {DateTimeOffset.UtcNow:MM/dd/yyyy HH:mm:ss.fff}]{ex}");
        }
    }
}