You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

231 lines
7.4 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. using IPA.Config;
  2. using IPA.Logging.Printers;
  3. using System;
  4. using System.Collections.Concurrent;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.Threading;
  8. namespace IPA.Logging
  9. {
  10. /// <summary>
  11. /// The default <see cref="Logger"/> implementation.
  12. /// </summary>
  13. public class StandardLogger : Logger
  14. {
  15. private static readonly IReadOnlyList<LogPrinter> defaultPrinters = new List<LogPrinter>()
  16. {
  17. new ColoredConsolePrinter()
  18. {
  19. Filter = LogLevel.DebugOnly,
  20. Color = ConsoleColor.Green,
  21. },
  22. new ColoredConsolePrinter()
  23. {
  24. Filter = LogLevel.InfoOnly,
  25. Color = ConsoleColor.White,
  26. },
  27. new ColoredConsolePrinter()
  28. {
  29. Filter = LogLevel.WarningOnly,
  30. Color = ConsoleColor.Yellow,
  31. },
  32. new ColoredConsolePrinter()
  33. {
  34. Filter = LogLevel.ErrorOnly,
  35. Color = ConsoleColor.Red,
  36. },
  37. new ColoredConsolePrinter()
  38. {
  39. Filter = LogLevel.CriticalOnly,
  40. Color = ConsoleColor.Magenta,
  41. },
  42. new GlobalLogFilePrinter()
  43. };
  44. private readonly string logName;
  45. private static readonly bool showSourceClass;
  46. /// <summary>
  47. /// All levels defined by this filter will be sent to loggers. All others will be ignored.
  48. /// </summary>
  49. public static LogLevel PrintFilter { get; set; }
  50. private readonly List<LogPrinter> printers = new List<LogPrinter>(defaultPrinters);
  51. private readonly Dictionary<string, StandardLogger> children = new Dictionary<string, StandardLogger>();
  52. static StandardLogger()
  53. {
  54. showSourceClass = ModPrefs.GetBool("IPA", "DebugShowCallSource", false, true);
  55. PrintFilter = ModPrefs.GetBool("IPA", "PrintDebug", false, true) ? LogLevel.All : LogLevel.InfoUp;
  56. }
  57. private StandardLogger(string mainName, string subName, params LogPrinter[] inherited)
  58. {
  59. logName = $"{mainName}/{subName}";
  60. printers = new List<LogPrinter>(inherited)
  61. {
  62. new PluginSubLogPrinter(mainName, subName)
  63. };
  64. if (logThread == null || !logThread.IsAlive)
  65. {
  66. logThread = new Thread(LogThread);
  67. logThread.Start();
  68. }
  69. }
  70. internal StandardLogger(string name)
  71. {
  72. logName = name;
  73. printers.Add(new PluginLogFilePrinter(name));
  74. if (logThread == null || !logThread.IsAlive)
  75. {
  76. logThread = new Thread(LogThread);
  77. logThread.Start();
  78. }
  79. }
  80. internal StandardLogger GetChild(string name)
  81. {
  82. if (!children.TryGetValue(name, out var child))
  83. {
  84. child = new StandardLogger(logName, name, printers.ToArray());
  85. children.Add(name, child);
  86. }
  87. return child;
  88. }
  89. /// <summary>
  90. /// Adds a log printer to the logger.
  91. /// </summary>
  92. /// <param name="printer">the printer to add</param>
  93. public void AddPrinter(LogPrinter printer)
  94. {
  95. printers.Add(printer);
  96. }
  97. /// <summary>
  98. /// Logs a specific message at a given level.
  99. /// </summary>
  100. /// <param name="level">the message level</param>
  101. /// <param name="message">the message to log</param>
  102. public override void Log(Level level, string message)
  103. {
  104. if (message == null)
  105. throw new ArgumentNullException(nameof(message));
  106. logQueue.Add(new LogMessage
  107. {
  108. Level = level,
  109. Message = message,
  110. Logger = this,
  111. Time = DateTime.Now
  112. });
  113. }
  114. /// <inheritdoc />
  115. /// <summary>
  116. /// An override to <see cref="M:IPA.Logging.Logger.Debug(System.String)" /> which shows the method that called it.
  117. /// </summary>
  118. /// <param name="message">the message to log</param>
  119. public override void Debug(string message)
  120. {
  121. // add source to message
  122. var stackFrame = new StackTrace().GetFrame(1);
  123. var method = stackFrame.GetMethod();
  124. var lineNo = stackFrame.GetFileLineNumber();
  125. var lineOffs = stackFrame.GetFileColumnNumber();
  126. base.Debug(showSourceClass
  127. ? $"{{{method.DeclaringType?.FullName}::{method.Name}({lineNo}:{lineOffs})}} {message}"
  128. : message);
  129. }
  130. private struct LogMessage
  131. {
  132. public Level Level;
  133. public StandardLogger Logger;
  134. public string Message;
  135. public DateTime Time;
  136. }
  137. private static readonly BlockingCollection<LogMessage> logQueue = new BlockingCollection<LogMessage>();
  138. private static Thread logThread;
  139. private static void LogThread()
  140. {
  141. var started = new HashSet<LogPrinter>();
  142. while (logQueue.TryTake(out var msg, Timeout.Infinite)) {
  143. foreach (var printer in msg.Logger.printers)
  144. {
  145. try
  146. {
  147. if (((byte)msg.Level & (byte)printer.Filter) != 0)
  148. {
  149. if (!started.Contains(printer))
  150. {
  151. printer.StartPrint();
  152. started.Add(printer);
  153. }
  154. printer.Print(msg.Level, msg.Time, msg.Logger.logName, msg.Message);
  155. }
  156. }
  157. catch (Exception e)
  158. {
  159. Console.WriteLine($"printer errored: {e}");
  160. }
  161. }
  162. if (logQueue.Count == 0)
  163. {
  164. foreach (var printer in started)
  165. {
  166. try
  167. {
  168. printer.EndPrint();
  169. }
  170. catch (Exception e)
  171. {
  172. Console.WriteLine($"printer errored: {e}");
  173. }
  174. }
  175. started.Clear();
  176. }
  177. }
  178. }
  179. internal static void StopLogThread()
  180. {
  181. logQueue.CompleteAdding();
  182. logThread.Join();
  183. }
  184. }
  185. /// <summary>
  186. /// A class providing extensions for various loggers.
  187. /// </summary>
  188. public static class LoggerExtensions
  189. {
  190. /// <summary>
  191. /// Gets a child logger, if supported.
  192. /// </summary>
  193. /// <param name="logger">the parent <see cref="Logger"/></param>
  194. /// <param name="name">the name of the child</param>
  195. /// <returns>the child logger</returns>
  196. public static Logger GetChildLogger(this Logger logger, string name)
  197. {
  198. if (logger is StandardLogger standardLogger)
  199. {
  200. return standardLogger.GetChild(name);
  201. }
  202. else
  203. {
  204. throw new InvalidOperationException();
  205. }
  206. }
  207. }
  208. }