LoggerTraceListener.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. using System;
  2. using System.Diagnostics;
  3. using System.Linq;
  4. using System.Reflection;
  5. using Harmony;
  6. namespace BepInEx.Logging
  7. {
  8. /// <summary>
  9. /// A trace listener that writes to an underlying <see cref="BaseLogger"/> instance.
  10. /// </summary>
  11. /// <inheritdoc cref="TraceListener"/>
  12. public class LoggerTraceListener : TraceListener
  13. {
  14. /// <summary>
  15. /// The logger instance that is being written to.
  16. /// </summary>
  17. public BaseLogger Logger { get; }
  18. static LoggerTraceListener()
  19. {
  20. try
  21. {
  22. TraceFixer.ApplyFix();
  23. }
  24. catch { } //ignore everything, if it's thrown an exception, we're using an assembly that has already fixed this
  25. }
  26. /// <param name="logger">The logger instance to write to.</param>
  27. public LoggerTraceListener(BaseLogger logger)
  28. {
  29. Logger = logger;
  30. }
  31. /// <summary>
  32. /// Writes a message to the underlying <see cref="BaseLogger"/> instance.
  33. /// </summary>
  34. /// <param name="message">The message to write.</param>
  35. public override void Write(string message)
  36. {
  37. Logger.Write(message);
  38. }
  39. /// <summary>
  40. /// Writes a message and a newline to the underlying <see cref="BaseLogger"/> instance.
  41. /// </summary>
  42. /// <param name="message">The message to write.</param>
  43. public override void WriteLine(string message)
  44. {
  45. Logger.WriteLine(message);
  46. }
  47. public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
  48. => TraceEvent(eventCache, source, eventType, id, string.Format(format, args));
  49. public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
  50. {
  51. LogLevel level;
  52. switch (eventType)
  53. {
  54. case TraceEventType.Critical:
  55. level = LogLevel.Fatal;
  56. break;
  57. case TraceEventType.Error:
  58. level = LogLevel.Error;
  59. break;
  60. case TraceEventType.Warning:
  61. level = LogLevel.Warning;
  62. break;
  63. case TraceEventType.Information:
  64. level = LogLevel.Info;
  65. break;
  66. case TraceEventType.Verbose:
  67. default:
  68. level = LogLevel.Debug;
  69. break;
  70. }
  71. Logger.Log(level, $"{source} : {message}");
  72. }
  73. /// <summary>
  74. /// 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.
  75. /// </summary>
  76. private static class TraceFixer
  77. {
  78. private static Type TraceImplType;
  79. private static object ListenersSyncRoot;
  80. private static TraceListenerCollection Listeners;
  81. private static PropertyInfo prop_AutoFlush;
  82. private static bool AutoFlush => (bool)prop_AutoFlush.GetValue(null, null);
  83. public static void ApplyFix()
  84. {
  85. TraceImplType = AppDomain.CurrentDomain.GetAssemblies()
  86. .First(x => x.GetName().Name == "System")
  87. .GetTypes()
  88. .First(x => x.Name == "TraceImpl");
  89. ListenersSyncRoot = AccessTools.Property(TraceImplType, "ListenersSyncRoot").GetValue(null, null);
  90. Listeners = (TraceListenerCollection)AccessTools.Property(TraceImplType, "Listeners").GetValue(null, null);
  91. prop_AutoFlush = AccessTools.Property(TraceImplType, "AutoFlush");
  92. HarmonyInstance instance = HarmonyInstance.Create("com.bepis.bepinex.tracefix");
  93. instance.Patch(
  94. typeof(Trace).GetMethod("DoTrace", BindingFlags.Static | BindingFlags.NonPublic),
  95. new HarmonyMethod(typeof(TraceFixer).GetMethod(nameof(DoTraceReplacement), BindingFlags.Static | BindingFlags.Public)),
  96. null);
  97. }
  98. public static bool DoTraceReplacement(string kind, Assembly report, string message)
  99. {
  100. string arg = string.Empty;
  101. try
  102. {
  103. arg = report.GetName().Name;
  104. }
  105. catch (MethodAccessException) { }
  106. TraceEventType type = (TraceEventType)Enum.Parse(typeof(TraceEventType), kind);
  107. lock (ListenersSyncRoot)
  108. {
  109. foreach (object obj in Listeners)
  110. {
  111. TraceListener traceListener = (TraceListener)obj;
  112. traceListener.TraceEvent(new TraceEventCache(), arg, type, 0, message);
  113. if (AutoFlush)
  114. {
  115. traceListener.Flush();
  116. }
  117. }
  118. }
  119. return false;
  120. }
  121. }
  122. }
  123. }