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
375 lines
13 KiB
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
|
|
{
|
|
|
|
/// <summary>日志记录程序。</summary>
|
|
public class Logger
|
|
{
|
|
|
|
private string _target = "";
|
|
private bool _enabled = true;
|
|
private Func<string, bool> _preoutput = null;
|
|
|
|
private bool _useconsole = true;
|
|
private bool _usefile = false;
|
|
private bool _uselock = false;
|
|
|
|
/// <summary>当前日志记录器的名称。</summary>
|
|
public virtual string Name { get; set; }
|
|
|
|
/// <summary>已启用。</summary>
|
|
public virtual bool Enabled { get; set; }
|
|
|
|
/// <summary>在后台线程处理日志。默认值:FALSE。</summary>
|
|
internal virtual bool Background { get; set; } = false;
|
|
|
|
/// <summary>使用控制台输出。默认值:TRUE。</summary>
|
|
public virtual bool UseConsole
|
|
{
|
|
get { return _useconsole; }
|
|
set { if (!_uselock) _useconsole = value; }
|
|
}
|
|
|
|
/// <summary>使用日志文件。默认值:FALSE。</summary>
|
|
public virtual bool UseFile
|
|
{
|
|
get { return _usefile; }
|
|
set { if (!_uselock) _usefile = value; }
|
|
}
|
|
|
|
/// <summary>异常。设置处理方法以替代 UseConsole 和 UseFile。</summary>
|
|
public virtual Event<Exception> OnException { get; set; }
|
|
|
|
/// <summary>错误。设置处理方法以替代 UseConsole 和 UseFile。</summary>
|
|
public virtual Event<string> OnError { get; set; }
|
|
|
|
/// <summary>注意。设置处理方法以替代 UseConsole 和 UseFile。</summary>
|
|
public virtual Event<string> OnWarning { get; set; }
|
|
|
|
/// <summary>注意。设置处理方法以替代 UseConsole 和 UseFile。</summary>
|
|
public virtual Event<string> OnInfo { get; set; }
|
|
|
|
/// <summary>文本。设置处理方法以替代 UseConsole 和 UseFile。</summary>
|
|
public virtual Event<string> OnText { get; set; }
|
|
|
|
/// <summary>调试。设置处理方法以替代 UseConsole 和 UseFile。</summary>
|
|
public virtual Event<string> OnDebug { get; set; }
|
|
|
|
private void Invoke(Action action)
|
|
{
|
|
if (Background)
|
|
{
|
|
RuntimeUtility.InBackground(action, true);
|
|
return;
|
|
}
|
|
try { action.Invoke(); } catch { }
|
|
}
|
|
|
|
/// <summary>记录异常。</summary>
|
|
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<ConsoleColor> color, object[] content, Event<string> 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);
|
|
});
|
|
|
|
/// <summary>记录错误。多个 Content 参数将以“ | ”分隔。</summary>
|
|
public virtual void Error(object sender, params object[] content) => Colorful(sender, "Error", cc.DarkRed, content, OnError);
|
|
|
|
/// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
|
|
public virtual void Warning(object sender, params object[] content) => Colorful(sender, "Warning", cc.DarkYellow, content, OnWarning);
|
|
|
|
/// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
|
|
public virtual void Info(object sender, params object[] content) => Colorful(sender, "Info", cc.DarkBlue, content, OnInfo);
|
|
|
|
/// <summary>记录文本。多个 Content 参数将以“ | ”分隔。</summary>
|
|
public virtual void Text(object sender, params object[] content) => Colorful(sender, "Text", null, content, OnText);
|
|
|
|
/// <summary>记录调试。多个 Content 参数将以“ | ”分隔。</summary>
|
|
[Conditional("DEBUG")]
|
|
public virtual void Debug(object sender, params object[] content) => Colorful(sender, "Debug", null, content, OnDebug);
|
|
|
|
/// <summary>创建新实例。</summary>
|
|
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();
|
|
|
|
/// <summary>获取用于保存日志文件的路径。</summary>
|
|
public static Func<string> 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);
|
|
|
|
/// <summary>获取日志文件路径发生错误时返回 NULL 值。</summary>
|
|
/// <remarks>默认例:<br/>d:\app\log\1970-01-01.log<br/>d:\www\app_data\log\1970-01-01.log</remarks>
|
|
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
|
|
|
|
/// <summary>内部的日志记录程序。</summary>
|
|
internal 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 静态调用。
|
|
|
|
/// <summary>使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。</summary>
|
|
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);
|
|
}
|
|
});
|
|
|
|
/// <summary>使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。</summary>
|
|
public static void Write<T>(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
|
|
|
|
}
|
|
|
|
}
|
|
|