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.

375 lines
13 KiB

4 years ago
4 years ago
  1. using Apewer.Internals;
  2. using Apewer.Models;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Text;
  8. using System.Threading;
  9. using cc = System.ConsoleColor;
  10. namespace Apewer
  11. {
  12. /// <summary>日志记录程序。</summary>
  13. public class Logger
  14. {
  15. private string _target = "";
  16. private bool _enabled = true;
  17. private Func<string, bool> _preoutput = null;
  18. private bool _useconsole = true;
  19. private bool _usefile = false;
  20. private bool _uselock = false;
  21. /// <summary>当前日志记录器的名称。</summary>
  22. public virtual string Name { get; set; }
  23. /// <summary>已启用。</summary>
  24. public virtual bool Enabled { get; set; }
  25. /// <summary>在后台线程处理日志。默认值:FALSE。</summary>
  26. internal virtual bool Background { get; set; } = false;
  27. /// <summary>使用控制台输出。默认值:TRUE。</summary>
  28. public virtual bool UseConsole
  29. {
  30. get { return _useconsole; }
  31. set { if (!_uselock) _useconsole = value; }
  32. }
  33. /// <summary>使用日志文件。默认值:FALSE。</summary>
  34. public virtual bool UseFile
  35. {
  36. get { return _usefile; }
  37. set { if (!_uselock) _usefile = value; }
  38. }
  39. /// <summary>异常。设置处理方法以替代 UseConsole 和 UseFile。</summary>
  40. public virtual Event<Exception> OnException { get; set; }
  41. /// <summary>错误。设置处理方法以替代 UseConsole 和 UseFile。</summary>
  42. public virtual Event<string> OnError { get; set; }
  43. /// <summary>注意。设置处理方法以替代 UseConsole 和 UseFile。</summary>
  44. public virtual Event<string> OnWarning { get; set; }
  45. /// <summary>注意。设置处理方法以替代 UseConsole 和 UseFile。</summary>
  46. public virtual Event<string> OnInfo { get; set; }
  47. /// <summary>文本。设置处理方法以替代 UseConsole 和 UseFile。</summary>
  48. public virtual Event<string> OnText { get; set; }
  49. /// <summary>调试。设置处理方法以替代 UseConsole 和 UseFile。</summary>
  50. public virtual Event<string> OnDebug { get; set; }
  51. private void Invoke(Action action)
  52. {
  53. if (Background)
  54. {
  55. RuntimeUtility.InBackground(action, true);
  56. return;
  57. }
  58. try { action.Invoke(); } catch { }
  59. }
  60. /// <summary>记录异常。</summary>
  61. public virtual void Exception(object sender, Exception exception) => Invoke(() =>
  62. {
  63. if (!Enabled) return;
  64. if (OnException != null)
  65. {
  66. OnException(sender, exception);
  67. return;
  68. }
  69. var item = new LogItem();
  70. item.Tag = "EXCEPTION";
  71. item.Color = ConsoleColor.DarkMagenta;
  72. item.Sender = sender;
  73. item.Exception = exception;
  74. if (exception == null)
  75. {
  76. item.Content = "无效异常。";
  77. }
  78. else
  79. {
  80. var type = exception.GetType().FullName;
  81. try
  82. {
  83. item.Content = MergeContent(type, exception.Message);
  84. }
  85. catch
  86. {
  87. item.Content = $"获取异常 {type} 的消息时再次引发了异常。";
  88. }
  89. }
  90. if (UseConsole) ToConsole(item);
  91. if (UseFile) ToFile(item, this);
  92. });
  93. private void Colorful(object sender, string tag, Nullable<ConsoleColor> color, object[] content, Event<string> defined) => Invoke(() =>
  94. {
  95. if (!Enabled) return;
  96. if (defined != null)
  97. {
  98. defined.Invoke(sender, MergeContent(content));
  99. return;
  100. }
  101. var item = new LogItem();
  102. item.Sender = sender;
  103. item.Tag = tag;
  104. if (color != null) item.Color = color.Value;
  105. item.Content = MergeContent(content);
  106. if (UseConsole) ToConsole(item);
  107. if (UseFile) ToFile(item, this);
  108. });
  109. /// <summary>记录错误。多个 Content 参数将以“ | ”分隔。</summary>
  110. public virtual void Error(object sender, params object[] content) => Colorful(sender, "Error", cc.DarkRed, content, OnError);
  111. /// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
  112. public virtual void Warning(object sender, params object[] content) => Colorful(sender, "Warning", cc.DarkYellow, content, OnWarning);
  113. /// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
  114. public virtual void Info(object sender, params object[] content) => Colorful(sender, "Info", cc.DarkBlue, content, OnInfo);
  115. /// <summary>记录文本。多个 Content 参数将以“ | ”分隔。</summary>
  116. public virtual void Text(object sender, params object[] content) => Colorful(sender, "Text", null, content, OnText);
  117. /// <summary>记录调试。多个 Content 参数将以“ | ”分隔。</summary>
  118. [Conditional("DEBUG")]
  119. public virtual void Debug(object sender, params object[] content) => Colorful(sender, "Debug", null, content, OnDebug);
  120. /// <summary>创建新实例。</summary>
  121. public Logger() { }
  122. private Logger(string name, bool useConsole, bool useFile, bool enabled)
  123. {
  124. Name = name;
  125. _useconsole = useConsole;
  126. _usefile = useFile;
  127. _uselock = true;
  128. _enabled = enabled;
  129. }
  130. #region 输出。
  131. internal static object FileLocker = new object();
  132. internal static object ConsoleLocker = new object();
  133. /// <summary>获取用于保存日志文件的路径。</summary>
  134. public static Func<Logger, string> FilePathGetter { get; set; }
  135. private static string MergeContent(params object[] content) => TextUtility.Join(" | ", content);
  136. private static string FormatSender(object sender)
  137. {
  138. if (sender == null) return null;
  139. if (sender is string) return sender as string;
  140. if (sender is Type) return ((Type)sender).FullName;
  141. var type = sender.GetType();
  142. var name = type.FullName;
  143. return name;
  144. }
  145. // 向控制台输出。
  146. private static void ToConsole(LogItem item)
  147. {
  148. var hasTag = !string.IsNullOrEmpty(item.Tag);
  149. var sender = FormatSender(item.Sender);
  150. var colorful = item.Color != null;
  151. lock (ConsoleLocker)
  152. {
  153. if (!colorful)
  154. {
  155. System.Console.WriteLine(ToText(item));
  156. return;
  157. }
  158. System.Console.ResetColor();
  159. System.Console.ForegroundColor = ConsoleColor.DarkGray;
  160. System.Console.Write(item.Clock);
  161. System.Console.ResetColor();
  162. if (hasTag)
  163. {
  164. System.Console.Write(" ");
  165. if (item.Color != null)
  166. {
  167. System.Console.BackgroundColor = item.Color.Value;
  168. System.Console.ForegroundColor = ConsoleColor.White;
  169. }
  170. System.Console.Write(" ");
  171. System.Console.Write(item.Tag);
  172. System.Console.Write(" ");
  173. }
  174. System.Console.ResetColor();
  175. if (!string.IsNullOrEmpty(sender))
  176. {
  177. System.Console.Write(" <");
  178. System.Console.Write(sender);
  179. System.Console.Write(">");
  180. }
  181. if (!string.IsNullOrEmpty(item.Content))
  182. {
  183. System.Console.Write(" ");
  184. System.Console.Write(item.Content);
  185. }
  186. System.Console.WriteLine();
  187. }
  188. }
  189. private static string ToText(LogItem item)
  190. {
  191. var sb = new StringBuilder();
  192. var sender = FormatSender(item.Sender);
  193. sb.Append(item.Clock);
  194. if (!string.IsNullOrEmpty(item.Tag))
  195. {
  196. sb.Append(" [");
  197. sb.Append(item.Tag);
  198. sb.Append("]");
  199. }
  200. if (!string.IsNullOrEmpty(sender))
  201. {
  202. sb.Append(" <");
  203. sb.Append(sender);
  204. sb.Append(">");
  205. }
  206. if (!string.IsNullOrEmpty(item.Content))
  207. {
  208. sb.Append(" ");
  209. sb.Append(item.Content);
  210. }
  211. return sb.ToString();
  212. }
  213. // 向日志文件输出文本,文件名按日期自动生成。
  214. private static string ToFile(string plain, Logger logger)
  215. {
  216. var text2 = TextUtility.Trim(plain) + "\r\n";
  217. lock (FileLocker)
  218. {
  219. var path = GetFilePath(logger);
  220. if (string.IsNullOrEmpty(path)) return "写入日志文件失败:无法获取日志文件路径。";
  221. var bytes = TextUtility.ToBinary(TextUtility.Merge(plain, "\r\n"));
  222. if (!StorageUtility.AppendFile(path, bytes)) return "写入日志文件失败。";
  223. }
  224. return null;
  225. }
  226. // 向日志文件输出文本,文件名按日期自动生成。
  227. private static string ToFile(LogItem item, Logger logger) => ToFile(ToText(item), logger);
  228. /// <summary>获取日志文件路径发生错误时返回 NULL 值。</summary>
  229. /// <remarks>默认例:<br/>d:\app\log\1970-01-01.log<br/>d:\www\app_data\log\1970-01-01.log</remarks>
  230. public static string GetFilePath(Logger logger = null)
  231. {
  232. var getter = FilePathGetter;
  233. if (getter != null) try { return getter.Invoke(logger); } catch { }
  234. // 找到 App_Data 目录。
  235. var appDir = RuntimeUtility.ApplicationPath;
  236. var dataDir = Path.Combine(appDir, "app_data");
  237. if (StorageUtility.DirectoryExists(dataDir)) appDir = dataDir;
  238. // 检查 Log 目录,不存在时创建,创建失败时返回。
  239. var logDir = Path.Combine(appDir, "log");
  240. if (!StorageUtility.AssureDirectory(logDir)) return null;
  241. // 文件不存在时创建新文件,无法创建时返回。
  242. var date = DateTime.Now.ToLucid(true, false, false, false);
  243. var filePath = Path.Combine(logDir, date + ".log");
  244. if (!StorageUtility.FileExists(filePath))
  245. {
  246. StorageUtility.WriteFile(filePath, TextUtility.Bom);
  247. if (!StorageUtility.FileExists(filePath)) return null;
  248. }
  249. // 返回 log 文件路径。
  250. return filePath;
  251. }
  252. #endregion
  253. #region 默认实列。
  254. private static Logger _default = new Logger("Apewer.Logger.Default", true, false, true);
  255. private static Logger _console = new Logger("Apewer.Logger.Console", true, false, true);
  256. private static Logger _web = new Logger("Apewer.Logger.Web", true, false, true);
  257. #if DEBUG
  258. internal static Logger _internals = new Logger("Apewer.Logger.Internals", true, true, true);
  259. #else
  260. internal static Logger _internals = new Logger("Apewer.Logger.Internals", true, false, false);
  261. #endif
  262. /// <summary>内部的日志记录程序。</summary>
  263. internal static Logger Internals { get => _internals; }
  264. /// <summary>默认的日志记录程序。</summary>
  265. public static Logger Default { get => _default; }
  266. /// <summary>仅输出到控制台的日志记录程序。</summary>
  267. public static Logger Console { get => _console; }
  268. /// <summary>用于 Web 的日志记录程序。</summary>
  269. public static Logger Web { get => _web; }
  270. #endregion
  271. #region 静态调用。
  272. /// <summary>使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。</summary>
  273. public static void Write(params object[] content) => Default.Invoke(() =>
  274. {
  275. var console = Default.UseConsole;
  276. var file = Default.UseFile;
  277. if (console || file)
  278. {
  279. var item = new LogItem();
  280. item.Content = MergeContent(content);
  281. if (console) ToConsole(item);
  282. if (file) ToFile(item, Default);
  283. }
  284. });
  285. /// <summary>使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。</summary>
  286. public static void Write<T>(params object[] content) => Default.Invoke(() =>
  287. {
  288. var console = Default.UseConsole;
  289. var file = Default.UseFile;
  290. if (console || file)
  291. {
  292. var item = new LogItem();
  293. item.Sender = typeof(T).FullName;
  294. item.Content = MergeContent(content);
  295. if (console) ToConsole(item);
  296. if (file) ToFile(item, Default);
  297. }
  298. });
  299. #endregion
  300. }
  301. }