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.

280 lines
9.8 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 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.Linq;
  8. using System.Threading;
  9. namespace IPA.Logging
  10. {
  11. /// <summary>
  12. /// The default (and standard) <see cref="Logger"/> implementation.
  13. /// </summary>
  14. /// <remarks>
  15. /// <see cref="StandardLogger"/> uses a multi-threaded approach to logging. All actual I/O is done on another thread,
  16. /// where all messaged are guaranteed to be logged in the order they appeared. It is up to the printers to format them.
  17. ///
  18. /// This logger supports child loggers. Use <see cref="LoggerExtensions.GetChildLogger"/> to safely get a child.
  19. /// The modification of printers on a parent are reflected down the chain.
  20. /// </remarks>
  21. public class StandardLogger : Logger
  22. {
  23. private static readonly List<LogPrinter> defaultPrinters = new List<LogPrinter>()
  24. {
  25. new ColoredConsolePrinter()
  26. {
  27. Filter = LogLevel.DebugOnly,
  28. Color = ConsoleColor.Green,
  29. },
  30. new ColoredConsolePrinter()
  31. {
  32. Filter = LogLevel.InfoOnly,
  33. Color = ConsoleColor.White,
  34. },
  35. new ColoredConsolePrinter()
  36. {
  37. Filter = LogLevel.WarningOnly,
  38. Color = ConsoleColor.Yellow,
  39. },
  40. new ColoredConsolePrinter()
  41. {
  42. Filter = LogLevel.ErrorOnly,
  43. Color = ConsoleColor.Red,
  44. },
  45. new ColoredConsolePrinter()
  46. {
  47. Filter = LogLevel.CriticalOnly,
  48. Color = ConsoleColor.Magenta,
  49. },
  50. new GlobalLogFilePrinter()
  51. };
  52. /// <summary>
  53. /// Adds to the default printer pool that all printers inherit from. Printers added this way will be passed every message from every logger.
  54. /// </summary>
  55. /// <param name="printer"></param>
  56. internal static void AddDefaultPrinter(LogPrinter printer)
  57. {
  58. defaultPrinters.Add(printer);
  59. }
  60. private readonly string logName;
  61. private static bool showSourceClass;
  62. /// <summary>
  63. /// All levels defined by this filter will be sent to loggers. All others will be ignored.
  64. /// </summary>
  65. public static LogLevel PrintFilter { get; set; } = LogLevel.All;
  66. private readonly List<LogPrinter> printers = new List<LogPrinter>();
  67. private readonly StandardLogger parent;
  68. private readonly Dictionary<string, StandardLogger> children = new Dictionary<string, StandardLogger>();
  69. /// <summary>
  70. /// Configures internal debug settings based on the config passed in.
  71. /// </summary>
  72. /// <param name="cfg"></param>
  73. internal static void Configure(SelfConfig cfg)
  74. {
  75. showSourceClass = cfg.Debug.ShowCallSource;
  76. PrintFilter = cfg.Debug.ShowDebug ? LogLevel.All : LogLevel.InfoUp;
  77. }
  78. private StandardLogger(StandardLogger parent, string subName)
  79. {
  80. logName = $"{parent.logName}/{subName}";
  81. this.parent = parent;
  82. printers = new List<LogPrinter>()
  83. {
  84. new PluginSubLogPrinter(parent.logName, subName)
  85. };
  86. if (logThread == null || !logThread.IsAlive)
  87. {
  88. logThread = new Thread(LogThread);
  89. logThread.Start();
  90. }
  91. }
  92. internal StandardLogger(string name)
  93. {
  94. logName = name;
  95. printers.Add(new PluginLogFilePrinter(name));
  96. if (logThread == null || !logThread.IsAlive)
  97. {
  98. logThread = new Thread(LogThread);
  99. logThread.Start();
  100. }
  101. }
  102. /// <summary>
  103. /// Gets a child printer with the given name, either constructing a new one or using one that was already made.
  104. /// </summary>
  105. /// <param name="name"></param>
  106. /// <returns>a child <see cref="StandardLogger"/> with the given sub-name</returns>
  107. internal StandardLogger GetChild(string name)
  108. {
  109. if (!children.TryGetValue(name, out var child))
  110. {
  111. child = new StandardLogger(this, name);
  112. children.Add(name, child);
  113. }
  114. return child;
  115. }
  116. /// <summary>
  117. /// Adds a log printer to the logger.
  118. /// </summary>
  119. /// <param name="printer">the printer to add</param>
  120. public void AddPrinter(LogPrinter printer)
  121. {
  122. printers.Add(printer);
  123. }
  124. /// <summary>
  125. /// Logs a specific message at a given level.
  126. /// </summary>
  127. /// <param name="level">the message level</param>
  128. /// <param name="message">the message to log</param>
  129. public override void Log(Level level, string message)
  130. {
  131. if (message == null)
  132. throw new ArgumentNullException(nameof(message));
  133. logQueue.Add(new LogMessage
  134. {
  135. Level = level,
  136. Message = message,
  137. Logger = this,
  138. Time = DateTime.Now
  139. });
  140. }
  141. /// <inheritdoc />
  142. /// <summary>
  143. /// An override to <see cref="M:IPA.Logging.Logger.Debug(System.String)" /> which shows the method that called it.
  144. /// </summary>
  145. /// <param name="message">the message to log</param>
  146. public override void Debug(string message)
  147. {
  148. // add source to message
  149. var stackFrame = new StackTrace(true).GetFrame(1);
  150. var method = stackFrame.GetMethod();
  151. var lineNo = stackFrame.GetFileLineNumber();
  152. var paramString = string.Join(", ", method.GetParameters().Select(p => p.ParameterType.FullName));
  153. base.Debug(showSourceClass
  154. ? $"{{{method.DeclaringType?.FullName}::{method.Name}({paramString}):{lineNo}}} {message}"
  155. : message);
  156. }
  157. private struct LogMessage
  158. {
  159. public Level Level;
  160. public StandardLogger Logger;
  161. public string Message;
  162. public DateTime Time;
  163. }
  164. private static readonly BlockingCollection<LogMessage> logQueue = new BlockingCollection<LogMessage>();
  165. private static Thread logThread;
  166. /// <summary>
  167. /// The log printer thread for <see cref="StandardLogger"/>.
  168. /// </summary>
  169. private static void LogThread()
  170. {
  171. AppDomain.CurrentDomain.ProcessExit += (sender, args) =>
  172. {
  173. StopLogThread();
  174. };
  175. var started = new HashSet<LogPrinter>();
  176. while (logQueue.TryTake(out var msg, Timeout.Infinite))
  177. {
  178. do
  179. {
  180. var logger = msg.Logger;
  181. IEnumerable<LogPrinter> printers = logger.printers;
  182. do
  183. {
  184. logger = logger.parent;
  185. if (logger != null)
  186. printers = printers.Concat(logger.printers);
  187. } while (logger != null);
  188. foreach (var printer in printers.Concat(defaultPrinters))
  189. {
  190. try
  191. {
  192. if (((byte) msg.Level & (byte) printer.Filter) != 0)
  193. {
  194. if (!started.Contains(printer))
  195. {
  196. printer.StartPrint();
  197. started.Add(printer);
  198. }
  199. printer.Print(msg.Level, msg.Time, msg.Logger.logName, msg.Message);
  200. }
  201. }
  202. catch (Exception e)
  203. {
  204. Console.WriteLine($"printer errored: {e}");
  205. }
  206. }
  207. }
  208. // wait for messages for 500ms before ending the prints
  209. while (logQueue.TryTake(out msg, TimeSpan.FromMilliseconds(500)));
  210. if (logQueue.Count == 0)
  211. {
  212. foreach (var printer in started)
  213. {
  214. try
  215. {
  216. printer.EndPrint();
  217. }
  218. catch (Exception e)
  219. {
  220. Console.WriteLine($"printer errored: {e}");
  221. }
  222. }
  223. started.Clear();
  224. }
  225. }
  226. }
  227. /// <summary>
  228. /// Stops and joins the log printer thread.
  229. /// </summary>
  230. internal static void StopLogThread()
  231. {
  232. logQueue.CompleteAdding();
  233. logThread.Join();
  234. }
  235. }
  236. /// <summary>
  237. /// A class providing extensions for various loggers.
  238. /// </summary>
  239. public static class LoggerExtensions
  240. {
  241. /// <summary>
  242. /// Gets a child logger, if supported. Currently the only defined and supported logger is <see cref="StandardLogger"/>, and most plugins will only ever receive this anyway.
  243. /// </summary>
  244. /// <param name="logger">the parent <see cref="Logger"/></param>
  245. /// <param name="name">the name of the child</param>
  246. /// <returns>the child logger</returns>
  247. public static Logger GetChildLogger(this Logger logger, string name)
  248. {
  249. if (logger is StandardLogger standardLogger)
  250. return standardLogger.GetChild(name);
  251. throw new InvalidOperationException();
  252. }
  253. }
  254. }