using System; using System.Diagnostics; using System.Linq; using System.Reflection; using Harmony; namespace BepInEx.Logging { /// /// A trace listener that writes to an underlying instance. /// /// public class LoggerTraceListener : TraceListener { /// /// The logger instance that is being written to. /// public BaseLogger Logger { get; } static LoggerTraceListener() { try { TraceFixer.ApplyFix(); } catch { } //ignore everything, if it's thrown an exception, we're using an assembly that has already fixed this } /// The logger instance to write to. public LoggerTraceListener(BaseLogger logger) { Logger = logger; } /// /// Writes a message to the underlying instance. /// /// The message to write. public override void Write(string message) { Logger.Write(message); } /// /// Writes a message and a newline to the underlying instance. /// /// The message to write. public override void WriteLine(string message) { Logger.WriteLine(message); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) => TraceEvent(eventCache, source, eventType, id, string.Format(format, args)); public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) { LogLevel level; switch (eventType) { case TraceEventType.Critical: level = LogLevel.Fatal; break; case TraceEventType.Error: level = LogLevel.Error; break; case TraceEventType.Warning: level = LogLevel.Warning; break; case TraceEventType.Information: level = LogLevel.Info; break; case TraceEventType.Verbose: default: level = LogLevel.Debug; break; } Logger.Log(level, $"{source} : {message}"); } /// /// This exists because the Mono implementation of is/was broken, and would call Write directly instead of calling TraceEvent. This class fixes that with a hook. /// private static class TraceFixer { private static Type TraceImplType; private static object ListenersSyncRoot; private static TraceListenerCollection Listeners; private static PropertyInfo prop_AutoFlush; private static bool AutoFlush => (bool)prop_AutoFlush.GetValue(null, null); public static void ApplyFix() { TraceImplType = AppDomain.CurrentDomain.GetAssemblies() .First(x => x.GetName().Name == "System") .GetTypes() .First(x => x.Name == "TraceImpl"); ListenersSyncRoot = AccessTools.Property(TraceImplType, "ListenersSyncRoot").GetValue(null, null); Listeners = (TraceListenerCollection)AccessTools.Property(TraceImplType, "Listeners").GetValue(null, null); prop_AutoFlush = AccessTools.Property(TraceImplType, "AutoFlush"); HarmonyInstance instance = HarmonyInstance.Create("com.bepis.bepinex.tracefix"); instance.Patch( typeof(Trace).GetMethod("DoTrace", BindingFlags.Static | BindingFlags.NonPublic), new HarmonyMethod(typeof(TraceFixer).GetMethod(nameof(DoTraceReplacement), BindingFlags.Static | BindingFlags.Public)), null); } public static bool DoTraceReplacement(string kind, Assembly report, string message) { string arg = string.Empty; try { arg = report.GetName().Name; } catch (MethodAccessException) { } TraceEventType type = (TraceEventType)Enum.Parse(typeof(TraceEventType), kind); lock (ListenersSyncRoot) { foreach (object obj in Listeners) { TraceListener traceListener = (TraceListener)obj; traceListener.TraceEvent(new TraceEventCache(), arg, type, 0, message); if (AutoFlush) { traceListener.Flush(); } } } return false; } } } }