using System; using System.IO; namespace Semmle.Util.Logging { /// /// The severity of a log message. /// public enum Severity { Trace = 1, Debug = 2, Info = 3, Warning = 4, Error = 5 } /// /// Log verbosity level. /// public enum Verbosity { Off = 0, Error = 1, Warning = 2, Info = 3, Debug = 4, Trace = 5, All = 6 } /// /// A logger. /// public interface ILogger : IDisposable { /// /// Log the given text with the given severity. /// void Log(Severity s, string text, int? threadId = null); void LogError(string text, int? threadId = null) => Log(Severity.Error, text, threadId); void LogWarning(string text, int? threadId = null) => Log(Severity.Warning, text, threadId); void LogInfo(string text, int? threadId = null) => Log(Severity.Info, text, threadId); void LogDebug(string text, int? threadId = null) => Log(Severity.Debug, text, threadId); } public static class LoggerExtensions { /// /// Log the given text with the given severity. /// public static void Log(this ILogger logger, Severity s, string text, params object?[] args) { logger.Log(s, string.Format(text, args)); } } /// /// A logger that outputs to a csharp.log /// file. /// public sealed class FileLogger : ILogger { private readonly StreamWriter writer; private readonly Verbosity verbosity; private readonly bool logThreadId; public FileLogger(Verbosity verbosity, string outputFile, bool logThreadId) { this.verbosity = verbosity; this.logThreadId = logThreadId; try { var dir = Path.GetDirectoryName(outputFile); if (!string.IsNullOrEmpty(dir) && !System.IO.Directory.Exists(dir)) Directory.CreateDirectory(dir); writer = new PidStreamWriter( new FileStream(outputFile, FileMode.Append, FileAccess.Write, FileShare.ReadWrite, 8192)); } catch (Exception ex) // lgtm[cs/catch-of-all-exceptions] { Console.Error.WriteLine("CodeQL: Couldn't initialise C# extractor output: " + ex.Message + "\n" + ex.StackTrace); Console.Error.Flush(); throw; } } public void Dispose() { writer.Dispose(); } private static string GetSeverityPrefix(Severity s) { return "[" + s.ToString().ToUpper() + "] "; } public void Log(Severity s, string text, int? threadId = null) { if (verbosity.Includes(s)) { threadId ??= Environment.CurrentManagedThreadId; var prefix = this.logThreadId ? $"[{threadId:D3}] " : ""; writer.WriteLine(prefix + GetSeverityPrefix(s) + text); } } } /// /// A logger that outputs to stdout/stderr. /// public sealed class ConsoleLogger : ILogger { private readonly Verbosity verbosity; private readonly bool logThreadId; public ConsoleLogger(Verbosity verbosity, bool logThreadId) { this.verbosity = verbosity; this.logThreadId = logThreadId; } public void Dispose() { } private static TextWriter GetConsole(Severity s) { return s == Severity.Error ? Console.Error : Console.Out; } private static string GetSeverityPrefix(Severity s) { switch (s) { case Severity.Trace: case Severity.Debug: case Severity.Info: return ""; case Severity.Warning: return "Warning: "; case Severity.Error: return "Error: "; default: throw new ArgumentOutOfRangeException(nameof(s)); } } public void Log(Severity s, string text, int? threadId = null) { if (verbosity.Includes(s)) { threadId ??= Environment.CurrentManagedThreadId; var prefix = this.logThreadId ? $"[{threadId:D3}] " : ""; GetConsole(s).WriteLine(prefix + GetSeverityPrefix(s) + text); } } } /// /// A combined logger. /// public sealed class CombinedLogger : ILogger { private readonly ILogger logger1; private readonly ILogger logger2; public CombinedLogger(ILogger logger1, ILogger logger2) { this.logger1 = logger1; this.logger2 = logger2; } public void Dispose() { logger1.Dispose(); logger2.Dispose(); } public void Log(Severity s, string text, int? threadId = null) { logger1.Log(s, text, threadId); logger2.Log(s, text, threadId); } } internal static class VerbosityExtensions { /// /// Whether a message with the given severity must be included /// for this verbosity level. /// public static bool Includes(this Verbosity v, Severity s) { switch (s) { case Severity.Trace: return v >= Verbosity.Trace; case Severity.Debug: return v >= Verbosity.Debug; case Severity.Info: return v >= Verbosity.Info; case Severity.Warning: return v >= Verbosity.Warning; case Severity.Error: return v >= Verbosity.Error; default: throw new ArgumentOutOfRangeException(nameof(s)); } } } }