using IPA.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using IPA;
using IPA.Logging.Printers;
namespace IPA.Logging
{
///
/// The default implimentation.
///
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 string logName;
private static readonly bool showSourceClass = true;
///
/// All levels defined by this filter will be sent to loggers. All others will be ignored.
///
public static LogLevel PrintFilter { get; set; } = LogLevel.InfoUp;
private List printers = new List(defaultPrinters);
private Dictionary children = new Dictionary();
static StandardLogger()
{
if (ModPrefs.GetBool("IPA", "PrintDebug", false, true))
PrintFilter = LogLevel.All;
showSourceClass = ModPrefs.GetBool("IPA", "DebugShowCallSource", false, true);
}
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 StandardLogger chld))
{
chld = new StandardLogger(logName, name, printers.ToArray());
children.Add(name, chld);
}
return chld;
}
///
/// Logs a specific message at a given level.
///
/// the message level
/// the message to log
public override void Log(Level level, string 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 stfm = new StackTrace().GetFrame(1).GetMethod();
if (showSourceClass)
base.Debug($"{{{stfm.DeclaringType.FullName}::{stfm.Name}}} {message}");
else
base.Debug(message);
}
internal struct LogMessage
{
public Level level;
public StandardLogger logger;
public string message;
public DateTime time;
}
private static BlockingCollection _logQueue = new BlockingCollection();
private static Thread _logThread;
private static void LogThread()
{
HashSet started = new HashSet();
while (_logQueue.TryTake(out LogMessage 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)
{
return (logger as StandardLogger).GetChild(name);
}
else
{
throw new InvalidOperationException();
}
}
}
}