|
|
using Apewer.Models; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text;
namespace Apewer {
/// <summary>日志记录程序。</summary>
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; private Action<LogItem>[] _actions = new Action<LogItem>[0]; private List<Action<LogItem>> _actions_list = new List<Action<LogItem>>(); private object _locker = new object();
/// <summary>当前日志记录器的名称。</summary>
public string Name { get; set; }
/// <summary>已启用。</summary>
public bool Enabled { get; set; }
/// <summary>在后台线程处理日志。默认值:FALSE。</summary>
internal bool Background { get; set; } = false;
/// <summary>使用控制台输出。默认值:TRUE。</summary>
public bool UseConsole { get { return _useconsole; } set { if (!_uselock) _useconsole = value; } }
/// <summary>使用日志文件。默认值:FALSE。</summary>
public bool UseFile { get { return _usefile; } set { if (_uselock) return; if (_cache_count > 0) Flush(); _usefile = value; } }
/// <summary>使用自定义方法。</summary>
public void UseAction(Action<LogItem> action) { if (action != null) { lock (_actions_list) { _actions_list.Add(action); _actions = _actions_list.ToArray(); } } }
/// <summary>日志文件的保留天数(不包含今天)。指定 <see cref="FilePathGetter"/> 时此属性无效。</summary>
/// <remarks>默认值:-1,保留所有日志。</remarks>
public int FileReserved { get => _reserved; set => _reserved = value; }
/// <summary>设置过期日志文件的回收程序。指定 <see cref="FilePathGetter"/> 时此属性无效。</summary>
/// <remarks>默认值:NULL,删除文件。</remarks>
public LogCollector Collector { get => _collector; set => _collector = value; }
void Inbackground(Action action) { if (Background) { RuntimeUtility.InBackground(() => { lock (_locker) { action.Invoke(); } }, true); return; } lock (_locker) { try { action.Invoke(); } catch { } } }
internal void Output(string tag, object[] content, Exception exception = null) { if (!Enabled) return; Inbackground(() => { var item = new LogItem(); item.Logger = this; item.Tag = tag; item.Content = content; item.Exception = exception;
var text = (UseConsole || UseFile) ? item.ToString() : null; if (UseConsole) { lock (ConsoleLocker) { System.Console.WriteLine(text); } } if (UseFile) { lock (_cache_locker) { if (_cache_capacity > 0) { _cache_array[_cache_count] = text; _cache_count += 1; if (_cache_count == _cache_capacity) Flush(); } else { ToFile(text, this); } } }
var actions = _actions; foreach (var action in actions) { action.Invoke(item); } }); }
/// <summary>创建新实例。</summary>
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;
/// <summary>设置缓存容量,指定为 0 可取消缓存,较大的日志缓存可能会耗尽内存。</summary>
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]; } }
/// <summary>将缓存的日志写入文件。</summary>
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();
/// <summary>获取用于保存日志文件的路径。</summary>
public static Func<Logger, string> FilePathGetter { get; set; }
// 向日志文件输出文本,文件名按日期自动生成。
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; }
/// <summary>获取日志文件路径发生错误时返回 NULL 值。</summary>
/// <remarks>默认例:<br/>d:\app\log\1970-01-01.log<br/>d:\www\app_data\log\1970-01-01.log</remarks>
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 fileName = (logger == null || logger.Name.IsEmpty()) ? $"{date}{FileExt}" : $"{date}_{logger.Name}{FileExt}";
// 写入文件。
var filePath = Path.Combine(logDir, fileName); 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 fileExt = Path.GetExtension(path); if (fileExt != FileExt) continue;
// 文件名格式
var fileName = Path.GetFileName(path); if (fileName[4] != '-') continue; if (fileName[7] != '-') continue; if (fileName[10] != '.' && fileName[10] != '_') continue;
// 确定文件名是属于当前的 Logger
if (logger.Name.IsEmpty()) { if (fileName.Length != 14) continue; // 2008-08-08.log
} else { if (fileName.Length < 16) continue; // 2008-08-08_a.log
var loggerName = fileName.Substring(11, fileName.Length - 15); if (loggerName != logger.Name) continue; }
// 从文件名解析日期
var dt = ClockUtility.Parse(fileName.Substring(0, 10)); if (dt == null) 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(null, true, false, true); private static Logger _console = new Logger(null, true, false, true); private static Logger _web = new Logger(null, true, false, true);
#if DEBUG
internal static Logger _internals = new Logger(null, true, false, true); #else
internal static Logger _internals = new Logger(null, true, false, false); #endif
/// <summary>内部的日志记录程序。</summary>
public static Logger Internals { get => _internals; }
/// <summary>默认的日志记录程序。</summary>
public static Logger Default { get => _default; }
/// <summary>仅输出到控制台的日志记录程序。</summary>
public static Logger Console { get => _console; }
/// <summary>用于 Web 的日志记录程序。</summary>
public static Logger Web { get => _web; }
#endregion
#region 静态。
const string FileExt = ".log";
/// <summary>使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。</summary>
public static void Write(params object[] content) => Default.Output(null, content, null);
/// <summary>使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。</summary>
public static void Write(Exception exception, params object[] content) => Default.Output(null, content, exception);
/// <summary>压缩日志文件的内容,另存为 ZIP 文件,并删除原日志文件。</summary>
public static void CollectToZip(string path) { if (!File.Exists(path)) return; var bytes = StorageUtility.ReadFile(path); if (bytes.Length > 0) { var zipDict = new Dictionary<string, byte[]>(); 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); }
/// <summary>压缩日志文件的内容,另存为 GZIP 文件,并删除原日志文件。</summary>
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
#region public
#endregion
}
}
|