Ver Fonte

Completely change logger workflow

Bepis há 6 anos atrás
pai
commit
69efae63ed

+ 4 - 0
BepInEx.Preloader/BepInEx.Preloader.csproj

@@ -42,6 +42,7 @@
     <Compile Include="Entrypoint.cs" />
     <Compile Include="Patcher\PatcherPlugin.cs" />
     <Compile Include="Preloader.cs" />
+    <Compile Include="PreloaderLogWriter.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="UnityPatches.cs" />
   </ItemGroup>
@@ -59,5 +60,8 @@
       <Name>Harmony</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Logger\" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>

+ 28 - 17
BepInEx.Preloader/Preloader.cs

@@ -22,7 +22,7 @@ namespace BepInEx.Preloader
 		/// <summary>
 		///     The log writer that is specific to the preloader.
 		/// </summary>
-		private static PreloaderLogWriter PreloaderLog { get; set; }
+		private static PreloaderConsoleListener PreloaderLog { get; set; }
 
 		public static void Run()
 		{
@@ -31,19 +31,20 @@ namespace BepInEx.Preloader
 				AllocateConsole();
 
 				UnityPatches.Apply();
+				
+				Logger.Sources.Add(TraceLogSource.CreateSource());
 
-				PreloaderLog =
-					new PreloaderLogWriter(
+				PreloaderLog = new PreloaderConsoleListener(
 						Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx")));
-				PreloaderLog.Enabled = true;
+
+				Logger.Listeners.Add(PreloaderLog);
+				
 
 				string consoleTile =
 					$"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
 				ConsoleWindow.Title = consoleTile;
 
-				Logger.SetLogger(PreloaderLog);
-
-				PreloaderLog.WriteLine(consoleTile);
+				Logger.LogMessage(consoleTile);
 
 				//See BuildInfoAttribute for more information about this section.
 				object[] attributes = typeof(BuildInfoAttribute).Assembly.GetCustomAttributes(typeof(BuildInfoAttribute), false);
@@ -52,10 +53,10 @@ namespace BepInEx.Preloader
 				{
 					var attribute = (BuildInfoAttribute)attributes[0];
 
-					PreloaderLog.WriteLine(attribute.Info);
+					Logger.LogMessage(attribute.Info);
 				}
 
-				Logger.Log(LogLevel.Message, "Preloader started");
+				Logger.LogMessage("Preloader started");
 
 				string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader");
 
@@ -66,22 +67,33 @@ namespace BepInEx.Preloader
 				AssemblyPatcher.PatchAndLoad(Paths.ManagedPath);
 
 				AssemblyPatcher.DisposePatchers();
+
+				Logger.LogMessage("Preloader finished");
+
+				UnityLogListener.WriteStringToUnityLog?.Invoke(PreloaderLog.ToString());
+
+				Logger.Listeners.Remove(PreloaderLog);
+				Logger.Listeners.Add(new ConsoleLogListener());
+
+				PreloaderLog.Dispose();
 			}
 			catch (Exception ex)
 			{
-				Logger.Log(LogLevel.Fatal, "Could not run preloader!");
-				Logger.Log(LogLevel.Fatal, ex);
-
-				PreloaderLog.Enabled = false;
-
 				try
 				{
+					Logger.Log(LogLevel.Fatal, "Could not run preloader!");
+					Logger.Log(LogLevel.Fatal, ex);
+
+					PreloaderLog?.Dispose();
+
 					if (!ConsoleWindow.IsAttatched)
 					{
 						//if we've already attached the console, then the log will already be written to the console
 						AllocateConsole();
 						Console.Write(PreloaderLog);
 					}
+
+					PreloaderLog = null;
 				}
 				finally
 				{
@@ -89,7 +101,7 @@ namespace BepInEx.Preloader
 						Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
 						PreloaderLog + "\r\n" + ex);
 
-					PreloaderLog.Dispose();
+					PreloaderLog?.Dispose();
 					PreloaderLog = null;
 				}
 			}
@@ -191,8 +203,7 @@ namespace BepInEx.Preloader
 		public static void PatchEntrypoint(ref AssemblyDefinition assembly)
 		{
 			if (assembly.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx")))
-				throw new Exception(
-					"BepInEx has been detected to be patched! Please unpatch before using a patchless variant!");
+				throw new Exception("BepInEx has been detected to be patched! Please unpatch before using a patchless variant!");
 
 			string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader");
 			string entrypointMethod = Config.GetEntry("entrypoint-method", ".cctor", "Preloader");

+ 91 - 0
BepInEx.Preloader/PreloaderLogWriter.cs

@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using BepInEx.ConsoleUtil;
+
+namespace BepInEx.Logging
+{
+	public class PreloaderConsoleListener : ILogListener
+	{
+		public List<LogEventArgs> LogEvents { get; } = new List<LogEventArgs>();
+		protected StringBuilder LogBuilder = new StringBuilder();
+
+		public static TextWriter StandardOut { get; set; }
+		protected PreloaderConsoleSource LoggerSource { get; set; }
+
+
+		public PreloaderConsoleListener(bool redirectConsole)
+		{
+			StandardOut = Console.Out;
+
+			if (redirectConsole)
+			{
+				LoggerSource = new PreloaderConsoleSource();
+
+				Logger.Sources.Add(LoggerSource);
+				Console.SetOut(LoggerSource);
+			}
+		}
+
+		public void LogEvent(object sender, LogEventArgs eventArgs)
+		{
+			LogEvents.Add(eventArgs);
+
+			string log = $"[{eventArgs.Level}:{((ILogSource)sender).SourceName}] {eventArgs.Data}\r\n";
+
+			LogBuilder.Append(log);
+
+			Kon.ForegroundColor = eventArgs.Level.GetConsoleColor();
+			ConsoleDirectWrite(log);
+			Kon.ForegroundColor = ConsoleColor.Gray;
+		}
+
+		public void ConsoleDirectWrite(string value)
+		{
+			StandardOut.Write(value);
+		}
+
+		public void ConsoleDirectWriteLine(string value)
+		{
+			StandardOut.WriteLine(value);
+		}
+
+		public override string ToString() => LogBuilder.ToString();
+
+		public void Dispose()
+		{
+
+			if (LoggerSource != null)
+			{
+				Console.SetOut(StandardOut);
+				Logger.Sources.Remove(LoggerSource);
+				LoggerSource.Dispose();
+				LoggerSource = null;
+			}
+		}
+	}
+
+	public class PreloaderConsoleSource : TextWriter, ILogSource
+	{
+		public override Encoding Encoding { get; } = Console.OutputEncoding;
+
+		public string SourceName { get; } = "BepInEx Preloader";
+
+		public event EventHandler<LogEventArgs> LogEvent;
+
+		public override void Write(object value)
+			=> LogEvent?.Invoke(this, new LogEventArgs(value, LogLevel.Info, this));
+
+		public override void Write(string value)
+			=> Write((object)value);
+
+		public override void WriteLine() { }
+
+		public override void WriteLine(object value)
+			=> Write(value);
+
+		public override void WriteLine(string value)
+			=> Write(value);
+	}
+}

+ 8 - 4
BepInEx/BepInEx.csproj

@@ -57,12 +57,16 @@
     <Compile Include="ConsoleUtil\SafeConsole.cs" />
     <Compile Include="Bootstrap\Chainloader.cs" />
     <Compile Include="Contract\BaseUnityPlugin.cs" />
-    <Compile Include="Logging\BaseLogger.cs" />
+    <Compile Include="Logging\LogEventArgs.cs" />
     <Compile Include="Logging\Logger.cs" />
     <Compile Include="Logging\LogLevel.cs" />
-    <Compile Include="Logging\PreloaderLogWriter.cs" />
-    <Compile Include="Logging\LoggerTraceListener.cs" />
-    <Compile Include="Logging\UnityLogWriter.cs" />
+    <Compile Include="Logging\ILogListener.cs" />
+    <Compile Include="Logging\ILogSource.cs" />
+    <Compile Include="Logging\ManualLogSource.cs" />
+    <Compile Include="Logging\TraceLogSource.cs" />
+    <Compile Include="Logging\ConsoleLogListener.cs" />
+    <Compile Include="Logging\UnityLogListener.cs" />
+    <Compile Include="Logging\UnityLogSource.cs" />
     <Compile Include="Paths.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Bootstrap\TypeLoader.cs" />

+ 16 - 10
BepInEx/Bootstrap/Chainloader.cs

@@ -8,7 +8,6 @@ using System.Text;
 using BepInEx.Logging;
 using UnityEngine;
 using UnityInjector.ConsoleUtil;
-using UnityLogWriter = BepInEx.Logging.UnityLogWriter;
 
 namespace BepInEx.Bootstrap
 {
@@ -25,7 +24,7 @@ namespace BepInEx.Bootstrap
 		/// <summary>
 		/// The GameObject that all plugins are attached to as components.
 		/// </summary>
-		public static GameObject ManagerObject { get; private set; } = new GameObject("BepInEx_Manager");
+		public static GameObject ManagerObject { get; private set; }
 
 
 		private static bool _loaded = false;
@@ -52,14 +51,23 @@ namespace BepInEx.Bootstrap
 
 				ConsoleEncoding.ConsoleCodePage = (uint)Encoding.UTF8.CodePage;
 				Console.OutputEncoding = Encoding.UTF8;
+				Logger.Listeners.Add(new ConsoleLogListener());
 			}
 
-			UnityLogWriter unityLogWriter = new UnityLogWriter();
+			//Fix for standard output getting overwritten by UnityLogger
+			Console.SetOut(ConsoleWindow.StandardOut);
 
-			if (Logger.CurrentLogger != null && Logger.CurrentLogger is PreloaderLogWriter preloaderLogger)
-				unityLogWriter.WriteToLog($"{preloaderLogger}\r\n");
 
-			Logger.SetLogger(unityLogWriter);
+			Logger.Listeners.Add(new UnityLogListener());
+
+			if (!TraceLogSource.IsListening)
+				Logger.Sources.Add(TraceLogSource.CreateSource());
+
+			if (bool.Parse(Config.GetEntry("chainloader-log-unity-messages", "false", "BepInEx")))
+				Logger.Sources.Add(new UnityLogSource());
+
+
+			Logger.Log(LogLevel.Message, "Chainloader ready");
 
 			_initialized = true;
 		}
@@ -83,10 +91,6 @@ namespace BepInEx.Bootstrap
 
 			try
 			{
-				if (bool.Parse(Config.GetEntry("chainloader-log-unity-messages", "false", "BepInEx")))
-					UnityLogWriter.ListenUnityLogs();
-				
-
 				var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
 				if (productNameProp != null)
 					ConsoleWindow.Title =
@@ -94,6 +98,8 @@ namespace BepInEx.Bootstrap
 
 				Logger.Log(LogLevel.Message, "Chainloader started");
 
+				ManagerObject = new GameObject("BepInEx_Manager");
+
 				UnityEngine.Object.DontDestroyOnLoad(ManagerObject);
 
 

+ 1 - 1
BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs

@@ -13,7 +13,7 @@ namespace UnityInjector.ConsoleUtil
 	// using only safe (managed) code
 	// --------------------------------------------------
 
-	partial class ConsoleEncoding
+	internal partial class ConsoleEncoding
 	{
 		private byte[] _byteBuffer = new byte[256];
 		private char[] _charBuffer = new char[256];

+ 1 - 1
BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs

@@ -15,7 +15,7 @@ namespace UnityInjector.ConsoleUtil
 	// http://jonskeet.uk/csharp/ebcdic/
 	// using only safe (managed) code
 	// --------------------------------------------------
-	partial class ConsoleEncoding
+	internal partial class ConsoleEncoding
 	{
 		[DllImport("kernel32.dll")]
 		private static extern uint GetConsoleOutputCP();

+ 1 - 1
BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs

@@ -15,7 +15,7 @@ namespace UnityInjector.ConsoleUtil
 	// http://jonskeet.uk/csharp/ebcdic/
 	// using only safe (managed) code
 	// --------------------------------------------------
-	public partial class ConsoleEncoding : Encoding
+	internal partial class ConsoleEncoding : Encoding
 	{
 		private readonly uint _codePage;
 		public override int CodePage => (int)_codePage;

+ 7 - 4
BepInEx/ConsoleUtil/ConsoleWindow.cs

@@ -10,12 +10,14 @@ using System.Text;
 
 namespace UnityInjector.ConsoleUtil
 {
-	public class ConsoleWindow
+	internal class ConsoleWindow
 	{
 		public static bool IsAttatched { get; private set; }
 		private static IntPtr _cOut;
 		private static IntPtr _oOut;
 
+		public static TextWriter StandardOut { get; private set; }
+
 		public static void Attach()
 		{
 			if (IsAttatched)
@@ -121,12 +123,13 @@ namespace UnityInjector.ConsoleUtil
 		private static void Init()
 		{
 			var stdOut = Console.OpenStandardOutput();
-			var stdWriter = new StreamWriter(stdOut, Encoding.Default)
+			StandardOut = new StreamWriter(stdOut, Encoding.Default)
 			{
 				AutoFlush = true
 			};
-			Console.SetOut(stdWriter);
-			Console.SetError(stdWriter);
+
+			Console.SetOut(StandardOut);
+			Console.SetError(StandardOut);
 		}
 
 		[DllImport("kernel32.dll", SetLastError = true)]

+ 1 - 1
BepInEx/ConsoleUtil/SafeConsole.cs

@@ -8,7 +8,7 @@ using System.Reflection;
 
 namespace UnityInjector.ConsoleUtil
 {
-	public static class SafeConsole
+	internal static class SafeConsole
 	{
 		private static GetColorDelegate _getBackgroundColor;
 		private static GetColorDelegate _getForegroundColor;

+ 0 - 99
BepInEx/Logging/BaseLogger.cs

@@ -1,99 +0,0 @@
-using System;
-using System.IO;
-using System.Text;
-
-namespace BepInEx.Logging
-{
-	/// <summary>
-	/// The base implementation of a logging class.
-	/// </summary>
-	public abstract class BaseLogger : TextWriter
-	{
-		protected BaseLogger()
-		{
-			DisplayedLevels = GetDefaultLogLevel();
-		}
-
-		/// <summary>
-		/// The encoding that the underlying text writer should use. Defaults to UTF-8 BOM.
-		/// </summary>
-		public override Encoding Encoding { get; } = new UTF8Encoding(true);
-
-
-		/// <summary>
-		/// The handler for a entry logged event.
-		/// </summary>
-		/// <param name="entry">The text element of the log itself.</param>
-		/// <param name="show">Whether or not it should be dislpayed to the user.</param>
-		public delegate void EntryLoggedEventHandler(LogLevel level, object entry);
-
-		/// <summary>
-		/// The listener event for an entry being logged.
-		/// </summary>
-		public event EntryLoggedEventHandler EntryLogged;
-
-		/// <summary>
-		/// Retrieves the default log level to use for this logger.
-		/// </summary>
-		/// <returns>The default log level to use.</returns>
-		protected virtual LogLevel GetDefaultLogLevel()
-		{
-			LogLevel lowest;
-
-			try
-			{
-				lowest = (LogLevel)Enum.Parse(typeof(LogLevel), Config.GetEntry("logger-displayed-levels", nameof(LogLevel.Info), "BepInEx"));
-			}
-			catch
-			{
-				return LogLevel.All;
-			}
-
-			if (lowest == LogLevel.None)
-				return LogLevel.None;
-
-			LogLevel value = lowest;
-
-			foreach (LogLevel e in Enum.GetValues(typeof(LogLevel)))
-			{
-				if (lowest > e)
-					value |= e;
-			}
-
-			return value;
-		}
-
-		/// <summary>
-		/// A filter which is used to specify which log levels are not ignored by the logger.
-		/// </summary>
-		public LogLevel DisplayedLevels { get; set; }
-
-		private readonly object logLockObj = new object();
-
-		/// <summary>
-		/// Logs an entry to the Logger instance.
-		/// </summary>
-		/// <param name="level">The level of the entry.</param>
-		/// <param name="entry">The textual value of the entry.</param>
-		public virtual void Log(LogLevel level, object entry)
-		{
-			if ((DisplayedLevels & level) != LogLevel.None)
-			{
-				lock (logLockObj)
-				{
-					EntryLogged?.Invoke(level, entry);
-					WriteLine($"[{level.GetHighestLevel()}] {entry}");
-				}
-			}
-		}
-
-		/// <summary>
-		/// Logs an entry to the Logger instance, with a <see cref="LogLevel"/> of Message.
-		/// </summary>
-		/// <param name="entry">The text value of this log entry.</param>
-		public virtual void Log(object entry)
-		{
-			Log(LogLevel.Message, entry);
-		}
-	}
-}

+ 22 - 0
BepInEx/Logging/ConsoleLogListener.cs

@@ -0,0 +1,22 @@
+using System;
+using BepInEx.ConsoleUtil;
+
+namespace BepInEx.Logging
+{
+	/// <summary>
+	/// Logs entries using Unity specific outputs.
+	/// </summary>
+	public class ConsoleLogListener : ILogListener
+	{
+		public void LogEvent(object sender, LogEventArgs eventArgs)
+		{
+			string log = $"[{eventArgs.Level}:{((ILogSource)sender).SourceName}] {eventArgs.Data}\r\n";
+
+			Kon.ForegroundColor = eventArgs.Level.GetConsoleColor();
+			Console.Write(log);
+			Kon.ForegroundColor = ConsoleColor.Gray;
+		}
+
+		public void Dispose() { }
+	}
+}

+ 9 - 0
BepInEx/Logging/ILogListener.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace BepInEx.Logging
+{
+	public interface ILogListener : IDisposable
+	{
+		void LogEvent(object sender, LogEventArgs eventArgs);
+	}
+}

+ 11 - 0
BepInEx/Logging/ILogSource.cs

@@ -0,0 +1,11 @@
+using System;
+
+namespace BepInEx.Logging
+{
+	public interface ILogSource : IDisposable
+	{
+		string SourceName { get; }
+
+		event EventHandler<LogEventArgs> LogEvent;
+	}
+}

+ 20 - 0
BepInEx/Logging/LogEventArgs.cs

@@ -0,0 +1,20 @@
+using System;
+
+namespace BepInEx.Logging
+{
+	public class LogEventArgs : EventArgs
+	{
+		public object Data { get; protected set; }
+
+		public LogLevel Level { get; protected set; }
+
+		public ILogSource Source { get; protected set; }
+
+		public LogEventArgs(object data, LogLevel level, ILogSource source)
+		{
+			Data = data;
+			Level = level;
+			Source = source;
+		}
+	}
+}

+ 60 - 20
BepInEx/Logging/Logger.cs

@@ -1,4 +1,6 @@
-using BepInEx.Logging;
+using System;
+using System.Collections.Generic;
+using BepInEx.Logging;
 
 namespace BepInEx
 {
@@ -7,37 +9,75 @@ namespace BepInEx
 	/// </summary>
 	public static class Logger
 	{
-		/// <summary>
-		/// The current instance of a <see cref="BaseLogger"/> that is being used.
-		/// </summary>
-		public static BaseLogger CurrentLogger { get; set; }
+		private static readonly ManualLogSource InternalLogSource = new ManualLogSource("BepInEx");
 
-		/// <summary>
-		/// The listener event for an entry being logged.
-		/// </summary>
-		public static event BaseLogger.EntryLoggedEventHandler EntryLogged;
+		public static ICollection<ILogListener> Listeners { get; } = new List<ILogListener>();
+
+		public static ICollection<ILogSource> Sources { get; } = new LogSourceCollection()
+		{
+			InternalLogSource
+		};
+
+		private static void InternalLogEvent(object sender, LogEventArgs eventArgs)
+		{
+			foreach (var listener in Listeners)
+			{
+				listener?.LogEvent(sender, eventArgs);
+			}
+		}
 
 		/// <summary>
 		/// Logs an entry to the current logger instance.
 		/// </summary>
 		/// <param name="level">The level of the entry.</param>
 		/// <param name="entry">The textual value of the entry.</param>
-		public static void Log(LogLevel level, object entry)
+		public static void Log(LogLevel level, object data)
 		{
-			EntryLogged?.Invoke(level, entry);
-
-			CurrentLogger?.Log(level, entry);
+			InternalLogEvent(InternalLogSource, new LogEventArgs(data, level, InternalLogSource));
 		}
 
-		/// <summary>
-		/// Sets the instance being used by the static <see cref="Logger"/> class.
-		/// </summary>
-		/// <param name="logger">The instance to use in the static class.</param>
-		public static void SetLogger(BaseLogger logger)
+		public static void LogFatal(object data) => Log(LogLevel.Fatal, data);
+		public static void LogError(object data) => Log(LogLevel.Error, data);
+		public static void LogWarning(object data) => Log(LogLevel.Warning, data);
+		public static void LogMessage(object data) => Log(LogLevel.Message, data);
+		public static void LogInfo(object data) => Log(LogLevel.Info, data);
+		public static void LogDebug(object data) => Log(LogLevel.Debug, data);
+
+
+		private class LogSourceCollection : List<ILogSource>, ICollection<ILogSource>
 		{
-			CurrentLogger?.Dispose();
+			void ICollection<ILogSource>.Add(ILogSource item)
+			{
+				if (item == null)
+					throw new ArgumentNullException(nameof(item), "Log sources cannot be null when added to the source list.");
+
+				item.LogEvent += InternalLogEvent;
+
+				base.Add(item);
+			}
+
+			void ICollection<ILogSource>.Clear()
+			{
+				foreach (var item in base.ToArray())
+				{
+					((ICollection<ILogSource>)this).Remove(item);
+				}
+			}
+
+			bool ICollection<ILogSource>.Remove(ILogSource item)
+			{
+				if (item == null)
+					return false;
+
+				if (!base.Contains(item))
+					return false;
+
+				item.LogEvent -= InternalLogEvent;
+
+				base.Remove(item);
 
-			CurrentLogger = logger;
+				return true;
+			}
 		}
 	}
 }

+ 29 - 0
BepInEx/Logging/ManualLogSource.cs

@@ -0,0 +1,29 @@
+using System;
+
+namespace BepInEx.Logging
+{
+	public class ManualLogSource : ILogSource
+	{
+		public string SourceName { get; }
+		public event EventHandler<LogEventArgs> LogEvent;
+
+		public ManualLogSource(string sourceName)
+		{
+			SourceName = sourceName;
+		}
+
+		public void Log(LogLevel level, object data)
+		{
+			LogEvent?.Invoke(this, new LogEventArgs(data, level, this));
+		}
+
+		public void LogFatal(object data) => Log(LogLevel.Fatal, data);
+		public void LogError(object data) => Log(LogLevel.Error, data);
+		public void LogWarning(object data) => Log(LogLevel.Warning, data);
+		public void LogMessage(object data) => Log(LogLevel.Message, data);
+		public void LogInfo(object data) => Log(LogLevel.Info, data);
+		public void LogDebug(object data) => Log(LogLevel.Debug, data);
+
+		public void Dispose() { }
+	}
+}

+ 0 - 128
BepInEx/Logging/PreloaderLogWriter.cs

@@ -1,128 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Text;
-using BepInEx.Bootstrap;
-using BepInEx.ConsoleUtil;
-
-namespace BepInEx.Logging
-{
-	/// <summary>
-	/// A log writer specific to the <see cref="Preloader"/>.
-	/// </summary>
-	/// <inheritdoc cref="BaseLogger"/>
-	public class PreloaderLogWriter : BaseLogger
-	{
-		/// <summary>
-		/// The <see cref="System.Text.StringBuilder"/> which contains all logged entries, so it may be passed onto another log writer.
-		/// </summary>
-		public StringBuilder StringBuilder { get; protected set; } = new StringBuilder();
-
-		/// <summary>
-		/// Whether or not the log writer is redirecting console output, so it can be logged.
-		/// </summary>
-		public bool IsRedirectingConsole { get; protected set; }
-
-		protected TextWriter stdout;
-		protected LoggerTraceListener traceListener;
-
-		private bool _enabled = false;
-
-		/// <summary>
-		/// Whether or not the log writer is writing and/or redirecting.
-		/// </summary>
-		public bool Enabled
-		{
-			get => _enabled;
-			set
-			{
-				if (value)
-					Enable();
-				else
-					Disable();
-			}
-		}
-
-		/// <param name="redirectConsole">Whether or not to redirect the console standard output.</param>
-		public PreloaderLogWriter(bool redirectConsole)
-		{
-			IsRedirectingConsole = redirectConsole;
-
-			stdout = Console.Out;
-			traceListener = new LoggerTraceListener(this);
-		}
-
-		/// <summary>
-		/// Enables the log writer.
-		/// </summary>
-		public void Enable()
-		{
-			if (_enabled)
-				return;
-
-			if (IsRedirectingConsole)
-				Console.SetOut(this);
-			else
-				Console.SetOut(Null);
-
-			Trace.Listeners.Add(traceListener);
-
-			_enabled = true;
-		}
-
-		/// <summary>
-		/// Disables the log writer.
-		/// </summary>
-		public void Disable()
-		{
-			if (!_enabled)
-				return;
-
-			Console.SetOut(stdout);
-
-			Trace.Listeners.Remove(traceListener);
-
-			_enabled = false;
-		}
-
-		/// <summary>
-		/// Logs an entry to the Logger instance.
-		/// </summary>
-		/// <param name="level">The level of the entry.</param>
-		/// <param name="entry">The textual value of the entry.</param>
-		public override void Log(LogLevel level, object entry)
-		{
-			Kon.ForegroundColor = level.GetConsoleColor();
-			base.Log(level, entry);
-			Kon.ForegroundColor = ConsoleColor.Gray;
-		}
-
-		public override void Write(char value)
-		{
-			StringBuilder.Append(value);
-
-			stdout.Write(value);
-		}
-
-		public override void Write(string value)
-		{
-			StringBuilder.Append(value);
-
-			stdout.Write(value);
-		}
-
-		protected override void Dispose(bool disposing)
-		{
-			Disable();
-			StringBuilder.Length = 0;
-
-			traceListener?.Dispose();
-			traceListener = null;
-		}
-
-		public override string ToString()
-		{
-			return StringBuilder.ToString().Trim();
-		}
-	}
-}

+ 28 - 15
BepInEx/Logging/LoggerTraceListener.cs

@@ -3,6 +3,7 @@ using System.Diagnostics;
 using System.Linq;
 using System.Reflection;
 using Harmony;
+using UnityEngineInternal;
 
 namespace BepInEx.Logging
 {
@@ -10,14 +11,9 @@ namespace BepInEx.Logging
 	/// A trace listener that writes to an underlying <see cref="BaseLogger"/> instance.
 	/// </summary>
 	/// <inheritdoc cref="TraceListener"/>
-	public class LoggerTraceListener : TraceListener
+	public class TraceLogSource : TraceListener
 	{
-		/// <summary>
-		/// The logger instance that is being written to.
-		/// </summary>
-		public BaseLogger Logger { get; }
-
-		static LoggerTraceListener()
+		static TraceLogSource()
 		{
 			try
 			{
@@ -26,10 +22,28 @@ namespace BepInEx.Logging
 			catch { } //ignore everything, if it's thrown an exception, we're using an assembly that has already fixed this
 		}
 
+		public static bool IsListening { get; protected set; } = false;
+
+		private static TraceLogSource traceListener;
+
+		public static ILogSource CreateSource()
+		{
+			if (traceListener == null)
+			{
+				traceListener = new TraceLogSource();
+				Trace.Listeners.Add(traceListener);
+				IsListening = true;
+			}
+
+			return traceListener.LogSource;
+		}
+
+		protected ManualLogSource LogSource { get; }
+
 		/// <param name="logger">The logger instance to write to.</param>
-		public LoggerTraceListener(BaseLogger logger)
+		protected TraceLogSource()
 		{
-			Logger = logger;
+			LogSource = new ManualLogSource("Trace");
 		}
 
 		/// <summary>
@@ -38,7 +52,7 @@ namespace BepInEx.Logging
 		/// <param name="message">The message to write.</param>
 		public override void Write(string message)
 		{
-			Logger.Write(message);
+			LogSource.Log(LogLevel.None, message);
 		}
 
 		/// <summary>
@@ -47,7 +61,7 @@ namespace BepInEx.Logging
 		/// <param name="message">The message to write.</param>
 		public override void WriteLine(string message)
 		{
-			Logger.WriteLine(message);
+			LogSource.Log(LogLevel.None, $"{message}\r\n");
 		}
 
 		public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
@@ -77,7 +91,7 @@ namespace BepInEx.Logging
 					break;
 			}
 
-			Logger.Log(level, $"{source} : {message}");
+			LogSource.Log(level, $"{source} : {message}");
 		}
 
 		/// <summary>
@@ -113,12 +127,11 @@ namespace BepInEx.Logging
 
 				instance.Patch(
 					typeof(Trace).GetMethod("DoTrace", BindingFlags.Static | BindingFlags.NonPublic),
-					new HarmonyMethod(typeof(TraceFixer).GetMethod(nameof(DoTraceReplacement), BindingFlags.Static | BindingFlags.Public)),
-					null);
+					prefix: new HarmonyMethod(typeof(TraceFixer).GetMethod(nameof(DoTraceReplacement), BindingFlags.Static | BindingFlags.NonPublic)));
 			}
 
 
-			public static bool DoTraceReplacement(string kind, Assembly report, string message)
+			private static bool DoTraceReplacement(string kind, Assembly report, string message)
 			{
 				string arg = string.Empty;
 				try

+ 54 - 0
BepInEx/Logging/UnityLogListener.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace BepInEx.Logging
+{
+	/// <summary>
+	/// Logs entries using Unity specific outputs.
+	/// </summary>
+	public class UnityLogListener : ILogListener
+	{
+		internal static readonly Action<string> WriteStringToUnityLog;
+
+		[DllImport("mono.dll", EntryPoint = "mono_lookup_internal_call")]
+		private static extern IntPtr MonoLookupInternalCall(IntPtr gconstpointer);
+
+		static UnityLogListener()
+		{
+			foreach (MethodInfo methodInfo in typeof(UnityEngine.UnityLogWriter).GetMethods(BindingFlags.Static | BindingFlags.Public))
+			{
+				if (MonoLookupInternalCall(methodInfo.MethodHandle.Value) == IntPtr.Zero)
+					continue;
+
+				WriteStringToUnityLog = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), methodInfo);
+				break;
+			}
+		}
+
+		public void LogEvent(object sender, LogEventArgs eventArgs)
+		{
+			if (sender is UnityLogSource)
+				return;
+
+			string log = $"[{eventArgs.Level}:{((ILogSource)sender).SourceName}] {eventArgs.Data}\r\n";
+
+			WriteStringToUnityLog?.Invoke(log);
+		}
+
+		public void Dispose() { }
+	}
+}
+
+namespace UnityEngine
+{
+	internal sealed class UnityLogWriter
+	{
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		public static extern void WriteStringToUnityLogImpl(string s);
+
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		public static extern void WriteStringToUnityLog(string s);
+	}
+}

+ 86 - 0
BepInEx/Logging/UnityLogSource.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Reflection;
+using UnityEngine;
+
+namespace BepInEx.Logging
+{
+	/// <summary>
+	/// Logs entries using Unity specific outputs.
+	/// </summary>
+	public class UnityLogSource : ILogSource
+	{
+		public string SourceName { get; } = "Unity Log";
+		public event EventHandler<LogEventArgs> LogEvent;
+
+		public UnityLogSource()
+		{
+			InternalUnityLogMessage += unityLogMessageHandler;
+		}
+
+		private void unityLogMessageHandler(object sender, LogEventArgs eventArgs)
+		{
+			var newEventArgs = new LogEventArgs(eventArgs.Data, eventArgs.Level, this);
+			LogEvent?.Invoke(this, newEventArgs);
+		}
+
+		private bool disposed = false;
+		public void Dispose()
+		{
+			if (!disposed)
+			{
+				InternalUnityLogMessage -= unityLogMessageHandler;
+				disposed = true;
+			}
+		}
+
+		#region Static Unity handler
+
+		private static event EventHandler<LogEventArgs> InternalUnityLogMessage;
+
+		static UnityLogSource()
+		{
+			var callback = new Application.LogCallback(OnUnityLogMessageReceived);
+
+			EventInfo logEvent = typeof(Application).GetEvent("logMessageReceived", BindingFlags.Public | BindingFlags.Static);
+			if (logEvent != null)
+			{
+				logEvent.AddEventHandler(null, callback);
+				//UnsubscribeAction = () => logEvent.RemoveEventHandler(null, callback);
+			}
+			else
+			{
+				MethodInfo registerLogCallback = typeof(Application).GetMethod("RegisterLogCallback", BindingFlags.Public | BindingFlags.Static);
+				registerLogCallback.Invoke(null, new object[] { callback });
+				//UnsubscribeAction = () => registerLogCallback.Invoke(null, new object[] { null });
+			}
+		}
+
+		private static void OnUnityLogMessageReceived(string message, string stackTrace, LogType type)
+		{
+			LogLevel logLevel;
+
+			switch (type)
+			{
+				case LogType.Error:
+				case LogType.Assert:
+				case LogType.Exception:
+					logLevel = LogLevel.Error;
+					break;
+				case LogType.Warning:
+					logLevel = LogLevel.Warning;
+					break;
+				case LogType.Log:
+				default:
+					logLevel = LogLevel.Info;
+					break;
+			}
+
+			if (type == LogType.Exception)
+				message += $"\nStack trace:\n{stackTrace}";
+
+			InternalUnityLogMessage?.Invoke(null, new LogEventArgs(message, logLevel, null));
+		}
+
+		#endregion
+	}
+}

+ 0 - 125
BepInEx/Logging/UnityLogWriter.cs

@@ -1,125 +0,0 @@
-using System;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using BepInEx.ConsoleUtil;
-using UnityEngine;
-
-namespace BepInEx.Logging
-{
-	/// <summary>
-	/// Logs entries using Unity specific outputs.
-	/// </summary>
-	public class UnityLogWriter : BaseLogger
-	{
-		private static readonly Action<string> WriteStringToUnityLog;
-
-		[DllImport("mono.dll", EntryPoint = "mono_lookup_internal_call")]
-		private static extern IntPtr MonoLookupInternalCall(IntPtr gconstpointer);
-
-		static UnityLogWriter()
-		{
-			foreach (MethodInfo methodInfo in typeof(UnityEngine.UnityLogWriter).GetMethods(BindingFlags.Static | BindingFlags.Public))
-			{
-				if (MonoLookupInternalCall(methodInfo.MethodHandle.Value) == IntPtr.Zero)
-					continue;
-
-				WriteStringToUnityLog = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), methodInfo);
-				break;
-			}
-		}
-
-		/// <summary>
-		/// Writes a string specifically to the game output log.
-		/// </summary>
-		/// <param name="value">The value to write.</param>
-		public void WriteToLog(string value)
-		{
-			WriteStringToUnityLog?.Invoke(value);
-		}
-
-		protected void InternalWrite(string value)
-		{
-			Console.Write(value);
-			WriteToLog(value);
-		}
-
-		/// <summary>
-		/// Logs an entry to the Logger instance.
-		/// </summary>
-		/// <param name="level">The level of the entry.</param>
-		/// <param name="entry">The textual value of the entry.</param>
-		public override void Log(LogLevel level, object entry)
-		{
-			Kon.ForegroundColor = level.GetConsoleColor();
-			base.Log(level, entry);
-			Kon.ForegroundColor = ConsoleColor.Gray;
-
-			// If the display level got ignored, still write it to the log
-			if ((DisplayedLevels & level) == LogLevel.None)
-				WriteToLog($"[{level.GetHighestLevel()}] {entry}\r\n");
-		}
-
-		public override void WriteLine(string value) => InternalWrite($"{value}\r\n");
-		public override void Write(char value) => InternalWrite(value.ToString());
-		public override void Write(string value) => InternalWrite(value);
-
-		/// <summary>
-		/// Start listening to Unity's log message events and sending the messages to BepInEx logger.
-		/// </summary>
-		public static void ListenUnityLogs()
-		{
-			Type application = typeof(Application);
-
-			EventInfo logEvent = application.GetEvent("logMessageReceived", BindingFlags.Public | BindingFlags.Static);
-			if (logEvent != null)
-			{
-				logEvent.AddEventHandler(null, new Application.LogCallback(OnUnityLogMessageReceived));
-			}
-			else
-			{
-				MethodInfo registerLogCallback = application.GetMethod("RegisterLogCallback", BindingFlags.Public | BindingFlags.Static);
-				registerLogCallback.Invoke(null, new object[] { new Application.LogCallback(OnUnityLogMessageReceived) });
-			}
-		}
-
-		private static void OnUnityLogMessageReceived(string message, string stackTrace, LogType type)
-		{
-			LogLevel logLevel = LogLevel.Message;
-
-			switch (type)
-			{
-				case LogType.Error:
-				case LogType.Assert:
-				case LogType.Exception:
-					logLevel = LogLevel.Error;
-					break;
-				case LogType.Warning:
-					logLevel = LogLevel.Warning;
-					break;
-				case LogType.Log:
-				default:
-					logLevel = LogLevel.Info;
-					break;
-			}
-
-			Logger.Log(logLevel, message);
-			if (type == LogType.Exception)
-			{
-				Logger.Log(logLevel, $"Stack trace:\n{stackTrace}");
-			}
-		}
-	}
-}
-
-namespace UnityEngine
-{
-	internal sealed class UnityLogWriter
-	{
-		[MethodImpl(MethodImplOptions.InternalCall)]
-		public static extern void WriteStringToUnityLogImpl(string s);
-
-		[MethodImpl(MethodImplOptions.InternalCall)]
-		public static extern void WriteStringToUnityLog(string s);
-	}
-}