using Apewer.Internals; using Apewer.Models; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using cc = System.ConsoleColor; namespace Apewer { /// 日志记录程序。 public class Logger { private string _target = ""; private bool _enabled = true; private Func _preoutput = null; private bool _useconsole = true; private bool _usefile = false; private bool _uselock = false; /// 当前日志记录器的名称。 public virtual string Name { get; set; } /// 已启用。 public virtual bool Enabled { get; set; } /// 在后台线程处理日志。默认值:FALSE。 internal virtual bool Background { get; set; } = false; /// 使用控制台输出。默认值:TRUE。 public virtual bool UseConsole { get { return _useconsole; } set { if (!_uselock) _useconsole = value; } } /// 使用日志文件。默认值:FALSE。 public virtual bool UseFile { get { return _usefile; } set { if (!_uselock) _usefile = value; } } /// 异常。设置处理方法以替代 UseConsole 和 UseFile。 public virtual Event OnException { get; set; } /// 错误。设置处理方法以替代 UseConsole 和 UseFile。 public virtual Event OnError { get; set; } /// 注意。设置处理方法以替代 UseConsole 和 UseFile。 public virtual Event OnWarning { get; set; } /// 注意。设置处理方法以替代 UseConsole 和 UseFile。 public virtual Event OnInfo { get; set; } /// 文本。设置处理方法以替代 UseConsole 和 UseFile。 public virtual Event OnText { get; set; } /// 调试。设置处理方法以替代 UseConsole 和 UseFile。 public virtual Event OnDebug { get; set; } private void Invoke(Action action) { if (Background) { RuntimeUtility.InBackground(action, true); return; } try { action.Invoke(); } catch { } } /// 记录异常。 public virtual void Exception(object sender, Exception exception) => Invoke(() => { if (!Enabled) return; if (OnException != null) { OnException(sender, exception); return; } var item = new LogItem(); item.Tag = "EXCEPTION"; item.Color = ConsoleColor.DarkMagenta; item.Sender = sender; item.Exception = exception; if (exception == null) { item.Content = "无效异常。"; } else { var type = exception.GetType().FullName; try { item.Content = MergeContent(type, exception.Message); } catch { item.Content = $"获取异常 {type} 的消息时再次引发了异常。"; } } if (UseConsole) ToConsole(item); if (UseFile) ToFile(item, this); }); private void Colorful(object sender, string tag, Nullable color, object[] content, Event defined) => Invoke(() => { if (!Enabled) return; if (defined != null) { defined.Invoke(sender, MergeContent(content)); return; } var item = new LogItem(); item.Sender = sender; item.Tag = tag; if (color != null) item.Color = color.Value; item.Content = MergeContent(content); if (UseConsole) ToConsole(item); if (UseFile) ToFile(item, this); }); /// 记录错误。多个 Content 参数将以“ | ”分隔。 public virtual void Error(object sender, params object[] content) => Colorful(sender, "Error", cc.DarkRed, content, OnError); /// 记录警告。多个 Content 参数将以“ | ”分隔。 public virtual void Warning(object sender, params object[] content) => Colorful(sender, "Warning", cc.DarkYellow, content, OnWarning); /// 记录警告。多个 Content 参数将以“ | ”分隔。 public virtual void Info(object sender, params object[] content) => Colorful(sender, "Info", cc.DarkBlue, content, OnInfo); /// 记录文本。多个 Content 参数将以“ | ”分隔。 public virtual void Text(object sender, params object[] content) => Colorful(sender, "Text", null, content, OnText); /// 记录调试。多个 Content 参数将以“ | ”分隔。 [Conditional("DEBUG")] public virtual void Debug(object sender, params object[] content) => Colorful(sender, "Debug", null, content, OnDebug); /// 创建新实例。 public Logger() { } private Logger(string name, bool useConsole, bool useFile, bool enabled) { Name = name; _useconsole = useConsole; _usefile = useFile; _uselock = true; _enabled = enabled; } #region 输出。 internal static object FileLocker = new object(); internal static object ConsoleLocker = new object(); /// 获取用于保存日志文件的路径。 public static Func FilePathGetter { get; set; } private static string MergeContent(params object[] content) => TextUtility.Join(" | ", content); private static string FormatSender(object sender) { if (sender == null) return null; if (sender is string) return sender as string; if (sender is Type) return ((Type)sender).FullName; var type = sender.GetType(); var name = type.FullName; return name; } // 向控制台输出。 private static void ToConsole(LogItem item) { var hasTag = !string.IsNullOrEmpty(item.Tag); var sender = FormatSender(item.Sender); var colorful = item.Color != null; lock (ConsoleLocker) { if (!colorful) { System.Console.WriteLine(ToText(item)); return; } System.Console.ResetColor(); System.Console.ForegroundColor = ConsoleColor.DarkGray; System.Console.Write(item.Clock); System.Console.ResetColor(); if (hasTag) { System.Console.Write(" "); if (item.Color != null) { System.Console.BackgroundColor = item.Color.Value; System.Console.ForegroundColor = ConsoleColor.White; } System.Console.Write(" "); System.Console.Write(item.Tag); System.Console.Write(" "); } System.Console.ResetColor(); if (!string.IsNullOrEmpty(sender)) { System.Console.Write(" <"); System.Console.Write(sender); System.Console.Write(">"); } if (!string.IsNullOrEmpty(item.Content)) { System.Console.Write(" "); System.Console.Write(item.Content); } System.Console.WriteLine(); } } private static string ToText(LogItem item) { var sb = new StringBuilder(); var sender = FormatSender(item.Sender); sb.Append(item.Clock); if (!string.IsNullOrEmpty(item.Tag)) { sb.Append(" ["); sb.Append(item.Tag); sb.Append("]"); } if (!string.IsNullOrEmpty(sender)) { sb.Append(" <"); sb.Append(sender); sb.Append(">"); } if (!string.IsNullOrEmpty(item.Content)) { sb.Append(" "); sb.Append(item.Content); } return sb.ToString(); } // 向日志文件输出文本,文件名按日期自动生成。 private static string ToFile(string plain, Logger logger) { var text2 = TextUtility.Trim(plain) + "\r\n"; lock (FileLocker) { var path = GetFilePath(logger); if (string.IsNullOrEmpty(path)) return "写入日志文件失败:无法获取日志文件路径。"; var bytes = TextUtility.ToBinary(TextUtility.Merge(plain, "\r\n")); if (!StorageUtility.AppendFile(path, bytes)) return "写入日志文件失败。"; } return null; } // 向日志文件输出文本,文件名按日期自动生成。 private static string ToFile(LogItem item, Logger logger) => ToFile(ToText(item), logger); /// 获取日志文件路径发生错误时返回 NULL 值。 /// 默认例:
d:\app\log\1970-01-01.log
d:\www\app_data\log\1970-01-01.log
public static string GetFilePath(Logger logger = null) { var getter = FilePathGetter; if (getter != null) try { return getter.Invoke(); } catch { } // 找到 App_Data 目录。 var appDir = RuntimeUtility.ApplicationPath; var dataDir = Path.Combine(appDir, "app_data"); if (StorageUtility.DirectoryExists(dataDir)) appDir = dataDir; // 检查 Log 目录,不存在时创建,创建失败时返回。 var logDir = Path.Combine(appDir, "log"); if (!StorageUtility.AssureDirectory(logDir)) return null; // 文件不存在时创建新文件,无法创建时返回。 var date = DateTime.Now.ToLucid(true, false, false, false); var filePath = Path.Combine(logDir, date + ".log"); if (!StorageUtility.FileExists(filePath)) { StorageUtility.WriteFile(filePath, TextUtility.Bom); if (!StorageUtility.FileExists(filePath)) return null; } // 返回 log 文件路径。 return filePath; } #endregion #region 默认实列。 private static Logger _default = new Logger("Apewer.Logger.Default", true, false, true); private static Logger _console = new Logger("Apewer.Logger.Console", true, false, true); private static Logger _web = new Logger("Apewer.Logger.Web", true, false, true); #if DEBUG internal static Logger _internals = new Logger("Apewer.Logger.Internals", true, true, true); #else internal static Logger _internals = new Logger("Apewer.Logger.Internals", true, false, false); #endif /// 内部的日志记录程序。 internal static Logger Internals { get => _internals; } /// 默认的日志记录程序。 public static Logger Default { get => _default; } /// 仅输出到控制台的日志记录程序。 public static Logger Console { get => _console; } /// 用于 Web 的日志记录程序。 public static Logger Web { get => _web; } #endregion #region 静态调用。 /// 使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。 public static void Write(params object[] content) => Default.Invoke(() => { var console = Default.UseConsole; var file = Default.UseFile; if (console || file) { var item = new LogItem(); item.Content = MergeContent(content); if (console) ToConsole(item); if (file) ToFile(item, Default); } }); /// 使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。 public static void Write(params object[] content) => Default.Invoke(() => { var console = Default.UseConsole; var file = Default.UseFile; if (console || file) { var item = new LogItem(); item.Sender = typeof(T).FullName; item.Content = MergeContent(content); if (console) ToConsole(item); if (file) ToFile(item, Default); } }); #endregion } }