Browse Source

Merge pull request #66 from BepInEx/feature-logger-overhaul

Feature: Logger overhaul
Bepis 6 years ago
parent
commit
f1052ba9e9

+ 6 - 3
BepInEx.Preloader/BepInEx.Preloader.csproj

@@ -38,12 +38,14 @@
     <Reference Include="System.Core" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="Patcher\AssemblyPatcher.cs" />
+    <Compile Include="Patching\AssemblyPatcher.cs" />
     <Compile Include="Entrypoint.cs" />
-    <Compile Include="Patcher\PatcherPlugin.cs" />
+    <Compile Include="Patching\PatcherPlugin.cs" />
+    <Compile Include="RuntimeFixes\TraceFix.cs" />
     <Compile Include="Preloader.cs" />
+    <Compile Include="Logger\PreloaderLogWriter.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="UnityPatches.cs" />
+    <Compile Include="RuntimeFixes\UnityPatches.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\BepInEx\BepInEx.csproj">
@@ -59,5 +61,6 @@
       <Name>Harmony</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>

+ 92 - 0
BepInEx.Preloader/Logger/PreloaderLogWriter.cs

@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using BepInEx.ConsoleUtil;
+using BepInEx.Logging;
+
+namespace BepInEx.Preloader
+{
+	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);
+	}
+}

+ 2 - 1
BepInEx.Preloader/Patcher/AssemblyPatcher.cs

@@ -3,9 +3,10 @@ using System.Collections.Generic;
 using System.IO;
 using System.Reflection;
 using BepInEx.Logging;
+using BepInEx.Preloader.RuntimeFixes;
 using Mono.Cecil;
 
-namespace BepInEx.Preloader.Patcher
+namespace BepInEx.Preloader.Patching
 {
 	/// <summary>
 	///     Delegate used in patching assemblies.

+ 1 - 1
BepInEx.Preloader/Patcher/PatcherPlugin.cs

@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace BepInEx.Preloader.Patcher
+namespace BepInEx.Preloader.Patching
 {
 	/// <summary>
 	///     A single assembly patcher.

+ 34 - 18
BepInEx.Preloader/Preloader.cs

@@ -6,7 +6,8 @@ using System.Linq;
 using System.Reflection;
 using System.Text;
 using BepInEx.Logging;
-using BepInEx.Preloader.Patcher;
+using BepInEx.Preloader.Patching;
+using BepInEx.Preloader.RuntimeFixes;
 using Mono.Cecil;
 using Mono.Cecil.Cil;
 using UnityInjector.ConsoleUtil;
@@ -22,7 +23,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 +32,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 +54,12 @@ namespace BepInEx.Preloader
 				{
 					var attribute = (BuildInfoAttribute)attributes[0];
 
-					PreloaderLog.WriteLine(attribute.Info);
+					Logger.LogMessage(attribute.Info);
 				}
 
-				Logger.Log(LogLevel.Message, "Preloader started");
+				Logger.LogMessage($"Running under unity version {Process.GetCurrentProcess().MainModule.FileVersionInfo.FileVersion}");
+
+				Logger.LogMessage("Preloader started");
 
 				string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader");
 
@@ -66,22 +70,35 @@ 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();
+
+				Trace.TraceError("This should be an error");
 			}
 			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 +106,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 +208,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");

+ 74 - 0
BepInEx.Preloader/RuntimeFixes/TraceFix.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using Harmony;
+
+namespace BepInEx.Preloader.RuntimeFixes
+{
+	/// <summary>
+	/// This exists because the Mono implementation of <see cref="Trace"/> is/was broken, and would call Write directly instead of calling TraceEvent. This class fixes that with a <see cref="Harmony"/> hook.
+	/// </summary>
+	internal static class TraceFix
+	{
+		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),
+				prefix: new HarmonyMethod(typeof(TraceFix).GetMethod(nameof(DoTraceReplacement), BindingFlags.Static | BindingFlags.NonPublic)));
+		}
+
+
+		private 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;
+		}
+	}
+}

+ 7 - 1
BepInEx.Preloader/UnityPatches.cs

@@ -4,7 +4,7 @@ using System.Reflection;
 using BepInEx.Harmony;
 using Harmony;
 
-namespace BepInEx.Preloader
+namespace BepInEx.Preloader.RuntimeFixes
 {
 	internal static class UnityPatches
 	{
@@ -16,6 +16,12 @@ namespace BepInEx.Preloader
 		public static void Apply()
 		{
 			HarmonyWrapper.PatchAll(typeof(UnityPatches), HarmonyInstance);
+
+			try
+			{
+				TraceFix.ApplyFix();
+			}
+			catch { } //ignore everything, if it's thrown an exception, we're using an assembly that has already fixed this
 		}
 
 		[HarmonyPostfix]

+ 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;

+ 13 - 2
BepInEx/Contract/BaseUnityPlugin.cs

@@ -1,9 +1,20 @@
-using UnityEngine;
+using BepInEx.Logging;
+using UnityEngine;
 
 namespace BepInEx
 {
 	/// <summary>
 	/// The base plugin type that is used by the BepInEx plugin loader.
 	/// </summary>
-	public abstract class BaseUnityPlugin : MonoBehaviour { }
+	public abstract class BaseUnityPlugin : MonoBehaviour
+	{
+		protected ManualLogSource Logger { get; }
+
+		protected BaseUnityPlugin()
+		{
+			var metadata = MetadataHelper.GetMetadata(this);
+
+			Logger = BepInEx.Logger.CreateLogSource(metadata.Name);
+		}
+	}
 }

+ 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;
+		}
+	}
+}

+ 65 - 19
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,81 @@ 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; }
+		public static ICollection<ILogListener> Listeners { get; } = new List<ILogListener>();
 
-		/// <summary>
-		/// The listener event for an entry being logged.
-		/// </summary>
-		public static event BaseLogger.EntryLoggedEventHandler EntryLogged;
+		public static ICollection<ILogSource> Sources { get; } = new LogSourceCollection();
+
+		private static readonly ManualLogSource InternalLogSource = CreateLogSource("BepInEx");
+
+		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)
+		{
+			InternalLogSource.Log(level, data);
+		}
+
+		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);
+
+		public static ManualLogSource CreateLogSource(string sourceName)
 		{
-			EntryLogged?.Invoke(level, entry);
+			var source = new ManualLogSource(sourceName);
 
-			CurrentLogger?.Log(level, entry);
+			Sources.Add(source);
+
+			return source;
 		}
 
-		/// <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)
+
+		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;
+			}
 		}
 	}
 }

+ 0 - 150
BepInEx/Logging/LoggerTraceListener.cs

@@ -1,150 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
-using Harmony;
-
-namespace BepInEx.Logging
-{
-	/// <summary>
-	/// A trace listener that writes to an underlying <see cref="BaseLogger"/> instance.
-	/// </summary>
-	/// <inheritdoc cref="TraceListener"/>
-	public class LoggerTraceListener : TraceListener
-	{
-		/// <summary>
-		/// The logger instance that is being written to.
-		/// </summary>
-		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
-		}
-
-		/// <param name="logger">The logger instance to write to.</param>
-		public LoggerTraceListener(BaseLogger logger)
-		{
-			Logger = logger;
-		}
-
-		/// <summary>
-		/// Writes a message to the underlying <see cref="BaseLogger"/> instance.
-		/// </summary>
-		/// <param name="message">The message to write.</param>
-		public override void Write(string message)
-		{
-			Logger.Write(message);
-		}
-
-		/// <summary>
-		/// Writes a message and a newline to the underlying <see cref="BaseLogger"/> instance.
-		/// </summary>
-		/// <param name="message">The message to write.</param>
-		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}");
-		}
-
-		/// <summary>
-		/// This exists because the Mono implementation of <see cref="Trace"/> is/was broken, and would call Write directly instead of calling TraceEvent. This class fixes that with a <see cref="Harmony"/> hook.
-		/// </summary>
-		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;
-			}
-		}
-	}
-}

+ 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();
-		}
-	}
-}

+ 83 - 0
BepInEx/Logging/TraceLogSource.cs

@@ -0,0 +1,83 @@
+using System.Diagnostics;
+
+namespace BepInEx.Logging
+{
+	/// <summary>
+	/// A trace listener that writes to an underlying <see cref="BaseLogger"/> instance.
+	/// </summary>
+	/// <inheritdoc cref="TraceListener"/>
+	public class TraceLogSource : TraceListener
+	{
+		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>
+		protected TraceLogSource()
+		{
+			LogSource = new ManualLogSource("Trace");
+		}
+
+		/// <summary>
+		/// Writes a message to the underlying <see cref="BaseLogger"/> instance.
+		/// </summary>
+		/// <param name="message">The message to write.</param>
+		public override void Write(string message)
+		{
+			LogSource.Log(LogLevel.None, message);
+		}
+
+		/// <summary>
+		/// Writes a message and a newline to the underlying <see cref="BaseLogger"/> instance.
+		/// </summary>
+		/// <param name="message">The message to write.</param>
+		public override void WriteLine(string 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)
+			=> 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;
+			}
+
+			LogSource.Log(level, $"{source} : {message}");
+		}
+	}
+}

+ 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);
-	}
-}