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.

337 lines
12 KiB

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