File: ServerLogger.cs
Web Access
Project: ..\..\..\src\RazorSdk\Tasks\Microsoft.NET.Sdk.Razor.Tasks.csproj (Microsoft.NET.Sdk.Razor.Tasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Diagnostics;
using System.Globalization;
 
namespace Microsoft.NET.Sdk.Razor.Tool
{
    /// <summary>
    /// Class for logging information about what happens in the server and client parts of the
    /// Razor command line compiler and build tasks. Useful for debugging what is going on.
    /// </summary>
    /// <remarks>
    /// To use the logging, set the environment variable RAZORBUILDSERVER_LOG to the name
    /// of a file to log to. This file is logged to by both client and server components.
    /// </remarks>
    internal class ServerLogger
    {
        // Environment variable, if set, to enable logging and set the file to log to.
        private const string EnvironmentVariable = "RAZORBUILDSERVER_LOG";
 
        private static readonly Stream s_loggingStream;
        private static string s_prefix = "---";
 
        /// <summary>
        /// Static class initializer that initializes logging.
        /// </summary>
        static ServerLogger()
        {
            s_loggingStream = null;
 
            try
            {
                // Check if the environment
                var loggingFileName = Environment.GetEnvironmentVariable(EnvironmentVariable);
 
                if (loggingFileName != null)
                {
                    IsLoggingEnabled = true;
 
                    // If the environment variable contains the path of a currently existing directory,
                    // then use a process-specific name for the log file and put it in that directory.
                    // Otherwise, assume that the environment variable specifies the name of the log file.
                    if (Directory.Exists(loggingFileName))
                    {
                        loggingFileName = Path.Combine(loggingFileName, $"razorserver.{GetCurrentProcessId()}.log");
                    }
 
                    // Open allowing sharing. We allow multiple processes to log to the same file, so we use share mode to allow that.
                    s_loggingStream = new FileStream(loggingFileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
                }
            }
            catch (Exception e)
            {
                LogException(e, "Failed to create logging stream");
            }
        }
 
        public static bool IsLoggingEnabled { get; }
 
        /// <summary>
        /// Set the logging prefix that describes our role.
        /// Typically a 3-letter abbreviation. If logging happens before this, it's logged with "---".
        /// </summary>
        public static void Initialize(string outputPrefix)
        {
            s_prefix = outputPrefix;
        }
 
        /// <summary>
        /// Log an exception. Also logs information about inner exceptions.
        /// </summary>
        public static void LogException(Exception e, string reason)
        {
            if (IsLoggingEnabled)
            {
                Log("Exception '{0}' occurred during '{1}'. Stack trace:\r\n{2}", e.Message, reason, e.StackTrace);
 
                var innerExceptionLevel = 0;
 
                e = e.InnerException;
                while (e != null)
                {
                    Log("Inner exception[{0}] '{1}'. Stack trace: \r\n{1}", innerExceptionLevel, e.Message, e.StackTrace);
                    e = e.InnerException;
                    innerExceptionLevel += 1;
                }
            }
        }
 
        /// <summary>
        /// Log a line of text to the logging file, with string.Format arguments.
        /// </summary>
        public static void Log(string format, params object[] arguments)
        {
            if (IsLoggingEnabled)
            {
                Log(string.Format(CultureInfo.InvariantCulture, format, arguments));
            }
        }
 
        /// <summary>
        /// Log a line of text to the logging file.
        /// </summary>
        /// <param name="message"></param>
        public static void Log(string message)
        {
            if (IsLoggingEnabled)
            {
                var prefix = string.Format(CultureInfo.InvariantCulture, "{0} PID={1} TID={2} Ticks={3}: ", s_prefix,
                    GetCurrentProcessId(), Environment.CurrentManagedThreadId, Environment.TickCount);
 
                var output = prefix + message + "\r\n";
                var bytes = Encoding.UTF8.GetBytes(output);
 
                // Because multiple processes might be logging to the same file, we always seek to the end,
                // write, and flush.
                s_loggingStream.Seek(0, SeekOrigin.End);
                s_loggingStream.Write(bytes, 0, bytes.Length);
                s_loggingStream.Flush();
            }
        }
 
        private static int GetCurrentProcessId() =>
#if NET5_0_OR_GREATER
            Environment.ProcessId;
#else
            Process.GetCurrentProcess().Id;
#endif
    }
}