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.

251 lines
8.4 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
  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. internal static void AddDefaultPrinter(LogPrinter printer)
  53. {
  54. defaultPrinters.Add(printer);
  55. }
  56. private readonly string logName;
  57. private static bool showSourceClass;
  58. /// <summary>
  59. /// All levels defined by this filter will be sent to loggers. All others will be ignored.
  60. /// </summary>
  61. public static LogLevel PrintFilter { get; set; } = LogLevel.All;
  62. private readonly List<LogPrinter> printers = new List<LogPrinter>();
  63. private readonly StandardLogger parent;
  64. private readonly Dictionary<string, StandardLogger> children = new Dictionary<string, StandardLogger>();
  65. internal static void Configure(SelfConfig cfg)
  66. {
  67. showSourceClass = cfg.Debug.ShowCallSource;
  68. PrintFilter = cfg.Debug.ShowDebug ? LogLevel.All : LogLevel.InfoUp;
  69. }
  70. private StandardLogger(StandardLogger parent, string subName)
  71. {
  72. logName = $"{parent.logName}/{subName}";
  73. this.parent = parent;
  74. printers = new List<LogPrinter>()
  75. {
  76. new PluginSubLogPrinter(parent.logName, subName)
  77. };
  78. if (logThread == null || !logThread.IsAlive)
  79. {
  80. logThread = new Thread(LogThread);
  81. logThread.Start();
  82. }
  83. }
  84. internal StandardLogger(string name)
  85. {
  86. logName = name;
  87. printers.Add(new PluginLogFilePrinter(name));
  88. if (logThread == null || !logThread.IsAlive)
  89. {
  90. logThread = new Thread(LogThread);
  91. logThread.Start();
  92. }
  93. }
  94. internal StandardLogger GetChild(string name)
  95. {
  96. if (!children.TryGetValue(name, out var child))
  97. {
  98. child = new StandardLogger(this, name);
  99. children.Add(name, child);
  100. }
  101. return child;
  102. }
  103. /// <summary>
  104. /// Adds a log printer to the logger.
  105. /// </summary>
  106. /// <param name="printer">the printer to add</param>
  107. public void AddPrinter(LogPrinter printer)
  108. {
  109. printers.Add(printer);
  110. }
  111. /// <summary>
  112. /// Logs a specific message at a given level.
  113. /// </summary>
  114. /// <param name="level">the message level</param>
  115. /// <param name="message">the message to log</param>
  116. public override void Log(Level level, string message)
  117. {
  118. if (message == null)
  119. throw new ArgumentNullException(nameof(message));
  120. logQueue.Add(new LogMessage
  121. {
  122. Level = level,
  123. Message = message,
  124. Logger = this,
  125. Time = DateTime.Now
  126. });
  127. }
  128. /// <inheritdoc />
  129. /// <summary>
  130. /// An override to <see cref="M:IPA.Logging.Logger.Debug(System.String)" /> which shows the method that called it.
  131. /// </summary>
  132. /// <param name="message">the message to log</param>
  133. public override void Debug(string message)
  134. {
  135. // add source to message
  136. var stackFrame = new StackTrace().GetFrame(1);
  137. var method = stackFrame.GetMethod();
  138. var lineNo = stackFrame.GetFileLineNumber();
  139. var lineOffs = stackFrame.GetFileColumnNumber();
  140. base.Debug(showSourceClass
  141. ? $"{{{method.DeclaringType?.FullName}::{method.Name}({lineNo}:{lineOffs})}} {message}"
  142. : message);
  143. }
  144. private struct LogMessage
  145. {
  146. public Level Level;
  147. public StandardLogger Logger;
  148. public string Message;
  149. public DateTime Time;
  150. }
  151. private static readonly BlockingCollection<LogMessage> logQueue = new BlockingCollection<LogMessage>();
  152. private static Thread logThread;
  153. private static void LogThread()
  154. {
  155. var started = new HashSet<LogPrinter>();
  156. while (logQueue.TryTake(out var msg, Timeout.Infinite))
  157. {
  158. var logger = msg.Logger;
  159. IEnumerable<LogPrinter> printers = logger.printers;
  160. do
  161. {
  162. logger = logger.parent;
  163. if (logger != null)
  164. printers = printers.Concat(logger.printers);
  165. } while (logger != null);
  166. foreach (var printer in printers.Concat(defaultPrinters))
  167. {
  168. try
  169. {
  170. if (((byte)msg.Level & (byte)printer.Filter) != 0)
  171. {
  172. if (!started.Contains(printer))
  173. {
  174. printer.StartPrint();
  175. started.Add(printer);
  176. }
  177. printer.Print(msg.Level, msg.Time, msg.Logger.logName, msg.Message);
  178. }
  179. }
  180. catch (Exception e)
  181. {
  182. Console.WriteLine($"printer errored: {e}");
  183. }
  184. }
  185. if (logQueue.Count == 0)
  186. {
  187. foreach (var printer in started)
  188. {
  189. try
  190. {
  191. printer.EndPrint();
  192. }
  193. catch (Exception e)
  194. {
  195. Console.WriteLine($"printer errored: {e}");
  196. }
  197. }
  198. started.Clear();
  199. }
  200. }
  201. }
  202. internal static void StopLogThread()
  203. {
  204. logQueue.CompleteAdding();
  205. logThread.Join();
  206. }
  207. }
  208. /// <summary>
  209. /// A class providing extensions for various loggers.
  210. /// </summary>
  211. public static class LoggerExtensions
  212. {
  213. /// <summary>
  214. /// 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.
  215. /// </summary>
  216. /// <param name="logger">the parent <see cref="Logger"/></param>
  217. /// <param name="name">the name of the child</param>
  218. /// <returns>the child logger</returns>
  219. public static Logger GetChildLogger(this Logger logger, string name)
  220. {
  221. if (logger is StandardLogger standardLogger)
  222. return standardLogger.GetChild(name);
  223. throw new InvalidOperationException();
  224. }
  225. }
  226. }