using IPA.Config;
using IPA.Logging.Printers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace IPA.Logging
{
///
/// The default implementation.
///
public class StandardLogger : Logger
{
private static readonly IReadOnlyList defaultPrinters = new List()
{
new ColoredConsolePrinter()
{
Filter = LogLevel.DebugOnly,
Color = ConsoleColor.Green,
},
new ColoredConsolePrinter()
{
Filter = LogLevel.InfoOnly,
Color = ConsoleColor.White,
},
new ColoredConsolePrinter()
{
Filter = LogLevel.WarningOnly,
Color = ConsoleColor.Yellow,
},
new ColoredConsolePrinter()
{
Filter = LogLevel.ErrorOnly,
Color = ConsoleColor.Red,
},
new ColoredConsolePrinter()
{
Filter = LogLevel.CriticalOnly,
Color = ConsoleColor.Magenta,
},
new GlobalLogFilePrinter()
};
private readonly string logName;
private static readonly bool showSourceClass;
///
/// All levels defined by this filter will be sent to loggers. All others will be ignored.
///
public static LogLevel PrintFilter { get; set; }
private readonly List printers = new List(defaultPrinters);
private readonly Dictionary children = new Dictionary();
static StandardLogger()
{
showSourceClass = ModPrefs.GetBool("IPA", "DebugShowCallSource", false, true);
PrintFilter = ModPrefs.GetBool("IPA", "PrintDebug", false, true) ? LogLevel.All : LogLevel.InfoUp;
}
private StandardLogger(string mainName, string subName, params LogPrinter[] inherited)
{
logName = $"{mainName}/{subName}";
printers = new List(inherited)
{
new PluginSubLogPrinter(mainName, subName)
};
if (logThread == null || !logThread.IsAlive)
{
logThread = new Thread(LogThread);
logThread.Start();
}
}
internal StandardLogger(string name)
{
logName = name;
printers.Add(new PluginLogFilePrinter(name));
if (logThread == null || !logThread.IsAlive)
{
logThread = new Thread(LogThread);
logThread.Start();
}
}
internal StandardLogger GetChild(string name)
{
if (!children.TryGetValue(name, out var child))
{
child = new StandardLogger(logName, name, printers.ToArray());
children.Add(name, child);
}
return child;
}
///
/// Adds a log printer to the logger.
///
/// the printer to add
public void AddPrinter(LogPrinter printer)
{
printers.Add(printer);
}
///
/// Logs a specific message at a given level.
///
/// the message level
/// the message to log
public override void Log(Level level, string message)
{
if (message == null)
throw new ArgumentNullException(nameof(message));
logQueue.Add(new LogMessage
{
Level = level,
Message = message,
Logger = this,
Time = DateTime.Now
});
}
///
///
/// An override to which shows the method that called it.
///
/// the message to log
public override void Debug(string message)
{
// add source to message
var stackFrame = new StackTrace().GetFrame(1);
var method = stackFrame.GetMethod();
var lineNo = stackFrame.GetFileLineNumber();
var lineOffs = stackFrame.GetFileColumnNumber();
base.Debug(showSourceClass
? $"{{{method.DeclaringType?.FullName}::{method.Name}({lineNo}:{lineOffs})}} {message}"
: message);
}
private struct LogMessage
{
public Level Level;
public StandardLogger Logger;
public string Message;
public DateTime Time;
}
private static readonly BlockingCollection logQueue = new BlockingCollection();
private static Thread logThread;
private static void LogThread()
{
var started = new HashSet();
while (logQueue.TryTake(out var msg, Timeout.Infinite)) {
foreach (var printer in msg.Logger.printers)
{
try
{
if (((byte)msg.Level & (byte)printer.Filter) != 0)
{
if (!started.Contains(printer))
{
printer.StartPrint();
started.Add(printer);
}
printer.Print(msg.Level, msg.Time, msg.Logger.logName, msg.Message);
}
}
catch (Exception e)
{
Console.WriteLine($"printer errored: {e}");
}
}
if (logQueue.Count == 0)
{
foreach (var printer in started)
{
try
{
printer.EndPrint();
}
catch (Exception e)
{
Console.WriteLine($"printer errored: {e}");
}
}
started.Clear();
}
}
}
internal static void StopLogThread()
{
logQueue.CompleteAdding();
logThread.Join();
}
}
///
/// A class providing extensions for various loggers.
///
public static class LoggerExtensions
{
///
/// Gets a child logger, if supported.
///
/// the parent
/// the name of the child
/// the child logger
public static Logger GetChildLogger(this Logger logger, string name)
{
if (logger is StandardLogger standardLogger)
{
return standardLogger.GetChild(name);
}
else
{
throw new InvalidOperationException();
}
}
}
}