Files
codeql/csharp/extractor/Semmle.Util/Logger.cs

218 lines
6.2 KiB
C#

using System;
using System.IO;
namespace Semmle.Util.Logging
{
/// <summary>
/// The severity of a log message.
/// </summary>
public enum Severity
{
Trace = 1,
Debug = 2,
Info = 3,
Warning = 4,
Error = 5
}
/// <summary>
/// Log verbosity level.
/// </summary>
public enum Verbosity
{
Off = 0,
Error = 1,
Warning = 2,
Info = 3,
Debug = 4,
Trace = 5,
All = 6
}
/// <summary>
/// A logger.
/// </summary>
public interface ILogger : IDisposable
{
/// <summary>
/// Log the given text with the given severity.
/// </summary>
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
{
/// <summary>
/// Log the given text with the given severity.
/// </summary>
public static void Log(this ILogger logger, Severity s, string text, params object?[] args)
{
logger.Log(s, string.Format(text, args));
}
}
/// <summary>
/// A logger that outputs to a <code>csharp.log</code>
/// file.
/// </summary>
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);
}
}
}
/// <summary>
/// A logger that outputs to stdout/stderr.
/// </summary>
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);
}
}
}
/// <summary>
/// A combined logger.
/// </summary>
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
{
/// <summary>
/// Whether a message with the given severity must be included
/// for this verbosity level.
/// </summary>
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));
}
}
}
}