using Apewer.Internals; using Apewer.Models; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using static System.ConsoleColor; namespace Apewer { /// 日志记录程序。 public sealed class Logger { private bool _useconsole = true; private bool _usefile = false; private bool _uselock = false; private int _reserved = -1; private LogCollector _collector = null; private string _lastdate = null; /// 当前日志记录器的名称。 public string Name { get; set; } /// 已启用。 public bool Enabled { get; set; } /// 在后台线程处理日志。默认值:FALSE。 internal bool Background { get; set; } = false; /// 使用控制台输出。默认值:TRUE。 public bool UseConsole { get { return _useconsole; } set { if (!_uselock) _useconsole = value; } } /// 使用日志文件。默认值:FALSE。 public bool UseFile { get { return _usefile; } set { if (_uselock) return; if (_cache_count > 0) Flush(); _usefile = value; } } /// 日志文件的保留天数(不包含今天)。指定 时此属性无效。 /// 默认值:-1,保留所有日志。 public int FileReserved { get => _reserved; set => _reserved = value; } /// 设置过期日志文件的回收程序。指定 时此属性无效。 /// 默认值:NULL,删除文件。 public LogCollector Collector { get => _collector; set => _collector = value; } private void Invoke(Action action) { if (Background) { RuntimeUtility.InBackground(action, true); return; } try { action.Invoke(); } catch { } } internal void Colorful(object sender, string tag, Nullable color, object[] content, Exception exception = null) { if (!Enabled) return; Invoke(() => { var item = new LogItem(); item.Sender = sender; item.Tag = tag; item.Color = color; item.Content = MergeContent(content); item.Exception = exception; if (UseConsole) ToConsole(item); if (UseFile) { var plain = ToText(item); lock (_cache_locker) { if (_cache_capacity > 0) { _cache_array[_cache_count] = plain; _cache_count += 1; if (_cache_count == _cache_capacity) Flush(); } else { ToFile(plain, this); } } } }); } /// 记录异常。 internal void InnerException(object sender, Exception exception) { if (!Enabled) return; var type = null as string; var content = null as string; if (exception != null) { try { type = exception.GetType().Name; content = MergeContent(new object[] { type, exception.Message }); } catch { } } if (content == null) content = "无效的 Exception 实例。"; Colorful(sender, "Exception", DarkMagenta, new object[] { type, content }, exception); } /// 创建新实例。 public Logger() { Enabled = true; } private Logger(string name, bool useConsole, bool useFile, bool enabled) { Name = name; UseConsole = useConsole; UseFile = useFile; Enabled = enabled; } #region 文件输出缓存。 private object _cache_locker = new object(); private int _cache_capacity = 0; private int _cache_count = 0; private string[] _cache_array = null; /// 设置缓存容量,指定为 0 可取消缓存,较大的日志缓存可能会耗尽内存。 public void SetCache(int capacity) { lock (_cache_locker) { if (_cache_count > 0) Flush(); if (capacity == _cache_capacity) return; _cache_capacity = capacity > 0 ? capacity : 0; _cache_array = (capacity < 1) ? null : new string[capacity]; } } /// 将缓存的日志写入文件。 public void Flush() { lock (_cache_locker) { if (_cache_count < 1) return; var sb = new StringBuilder(); for (var i = 0; i < _cache_count; i++) { sb.Append(_cache_array[i]); sb.Append("\r\n"); } ToFile(sb.ToString(), this, false); _cache_count = 0; _cache_array = new string[_cache_capacity]; } } #endregion #region 输出。 internal static object FileLocker = new object(); internal static object ConsoleLocker = new object(); /// 获取用于保存日志文件的路径。 public static Func FilePathGetter { get; set; } private static string MergeContent(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).Name; return sender.GetType().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 = 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 = 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, bool crlf = true) { lock (FileLocker) { var path = GetFilePath(logger); if (string.IsNullOrEmpty(path)) return "写入日志文件失败:无法获取日志文件路径。"; var bytes = TextUtility.Bytes(crlf ? TextUtility.Merge(plain, "\r\n") : plain); if (!StorageUtility.AppendFile(path, bytes)) return "写入日志文件失败。"; } return null; } /// 获取日志文件路径发生错误时返回 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(logger); } 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 now = DateTime.Now; var date = now.Lucid(true, false, false, false); var filePath = Path.Combine(logDir, date + FileExt); if (!StorageUtility.FileExists(filePath)) { StorageUtility.WriteFile(filePath, TextUtility.Bom); if (!StorageUtility.FileExists(filePath)) return null; } // 检查过期文件。 if (logger != null && getter == null && logger._reserved > -1) { if (date != logger._lastdate) { logger._lastdate = date; RuntimeUtility.StartThread(() => CollectFiles(logger, now, logDir)); } } // 返回 log 文件路径。 return filePath; } static void CollectFiles(Logger logger, DateTime now, string logDir) { var reserved = logger._reserved; if (reserved < 0) return; var collector = logger._collector; var today = DateTime.Now.Date; var paths = StorageUtility.GetSubFiles(logDir); foreach (var path in paths) { var fileName = Path.GetFileNameWithoutExtension(path); var fileExt = Path.GetExtension(path); if (fileName.Length != 10) continue; if (fileName[4] != '-') continue; if (fileName[7] != '-') continue; if (fileExt != FileExt) continue; var dt = ClockUtility.ParseLucid(fileName); if (dt == null || !dt.HasValue) continue; var days = Convert.ToInt32(Convert.ToInt64((today - dt.Value).TotalMilliseconds) / 86400000L); if (days > reserved) { if (collector == null) StorageUtility.DeleteFile(path); else collector(path, days); } } } #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, false, true); #else internal static Logger _internals = new Logger("Apewer.Logger.Internals", true, false, false); #endif /// 内部的日志记录程序。 public 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 静态。 const string FileExt = ".log"; /// 使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。 public static void Write(params object[] content) => Default.Colorful(null, null, null, content, null); /// 使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。 public static void Write(params object[] content) => Default.Colorful(typeof(T), null, null, content, null); /// 使用 Logger.Default 写入日志,自动添加时间和日期。 public static void Write(Exception exception) => Default.InnerException(null, exception); /// 使用 Logger.Default 写入日志,自动添加时间和日期。 public static void Write(Exception exception) => Default.InnerException(typeof(T), exception); /// 压缩日志文件的内容,另存为 ZIP 文件,并删除原日志文件。 public static void CollectToZip(string path) { if (!File.Exists(path)) return; var bytes = StorageUtility.ReadFile(path); if (bytes.Length > 0) { var zipDict = new Dictionary(); zipDict.Add(Path.GetFileName(path), bytes); var zipData = BytesUtility.ToZip(zipDict); if (zipData != null && zipData.Length > 0) { var zipPath = path + ".zip"; StorageUtility.WriteFile(zipPath, zipData); } } StorageUtility.DeleteFile(path); } /// 压缩日志文件的内容,另存为 GZIP 文件,并删除原日志文件。 public static void CollectToGZip(string path) { if (!File.Exists(path)) return; var bytes = StorageUtility.ReadFile(path); if (bytes.Length > 0) { var gzipData = BytesUtility.ToGzip(bytes); if (gzipData != null || gzipData.Length > 0) { var gzipPath = path + ".gzip"; StorageUtility.WriteFile(gzipPath, gzipData); } } StorageUtility.DeleteFile(path); } #endregion } }