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.

222 lines
8.0 KiB

using Apewer.Internals;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
namespace Apewer
{
/// <summary>日志记录程序。</summary>
public class Logger
{
private string _key = Guid.NewGuid().ToString("n");
private string _target = "";
private bool _enabled = true;
private Func<string, bool> _preoutput = null;
/// <summary>已启用。</summary>
public virtual bool Enabled { get; set; }
/// <summary>使用控制台输出。默认值:TRUE。</summary>
public virtual bool UseConsole { get; set; } = true;
/// <summary>使用日志文件。默认值:FALSE。</summary>
public virtual bool UseFile { get; set; } = false;
/// <summary>输出前的检查,返回值将确认输出。</summary>
public virtual Func<string, bool> PreOutput { get; set; }
/// <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> OnText { get; set; }
/// <summary>调试。设置处理方法以替代 UseConsole 和 UseFile。</summary>
public virtual Event<string> OnDebug { get; set; }
private void Call<T>(string text, Event<T> defined, Action<string> action)
{
var pre = PreOutput;
if (pre != null)
{
var @continue = PreOutput(text);
if (!@continue) return;
}
if (defined == null)
{
if (UseConsole) ToConsole(text);
if (UseFile) ToFile(text);
}
else
{
try
{
action(text);
}
catch { }
}
}
/// <summary>记录异常。</summary>
public virtual void Exception(object sender, Exception exception)
{
string text;
if (exception == null)
{
text = "无效异常。";
}
else
{
try { text = TextUtility.Join(" | ", exception.GetType().FullName, exception.Message); }
catch { text = "获取异常时再次引发了异常。"; }
}
Call(MergeText(true, "EXCEPTION", text), OnException, (t) => OnException(sender, exception));
}
/// <summary>记录错误。多个 Content 参数将以“ | ”分隔。</summary>
public virtual void Error(object sender, params object[] content)
{
Call(MergeText(true, "ERROR", content), OnError, (t) => OnError(sender, t));
}
/// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
public virtual void Warning(object sender, params object[] content)
{
Call(MergeText(true, "WARNING", content), OnWarning, (t) => OnWarning(sender, t));
}
/// <summary>记录文本。多个 Content 参数将以“ | ”分隔。</summary>
public virtual void Text(object sender, params object[] content)
{
Call(MergeText(true, "TEXT", content), OnText, (t) => OnText(sender, t));
}
/// <summary>记录调试。多个 Content 参数将以“ | ”分隔。</summary>
public virtual void Debug(object sender, params object[] content)
{
Call(MergeText(true, "DEBUG", content), OnDebug, (t) => OnDebug(sender, t));
}
#region 默认实列。
private static Logger _default = new Logger();
/// <summary>默认的日志记录程序,将信息写入控制台。</summary>
public static Logger Default { get => _default; }
#endregion
#region 输出。
private static object FileLocker = new object();
private static object ConsoleLocker = new object();
private static string MergeText(bool withClock, string tag, params object[] content)
{
var sb = new StringBuilder();
if (withClock)
{
sb.Append(DateTimeUtility.NowLucid);
sb.Append(" ");
}
if (!string.IsNullOrEmpty(tag))
{
sb.Append(DateTimeUtility.NowLucid);
sb.Append(tag);
sb.Append(" ");
}
sb.Append(TextUtility.Join(" | ", content));
sb.Append("\r\n");
return sb.ToString();
}
/// <summary>向控制台输出文本。</summary>
public static void ToConsole(string text)
{
lock (ConsoleLocker)
{
if (string.IsNullOrEmpty(text)) Console.WriteLine();
else Console.WriteLine(text);
}
}
/// <summary>向日志文件输出文本,文件名按日期自动生成。输出失败时返回错误信息。</summary>
public static string ToFile(string text) => ToFile(text, false);
/// <summary>向日志文件输出文本,文件名按日期自动生成。输出失败时返回错误信息。</summary>
public static string ToFile(string text, bool withConsole)
{
if (withConsole) ToConsole(text);
lock (FileLocker)
{
var path = GetFileDir();
if (string.IsNullOrEmpty(path))
{
var msg = "写入日志文件失败:无法获取日志文件路径。";
if (withConsole) ToConsole(msg);
return msg;
}
var bytes = TextUtility.ToBinary(TextUtility.Merge(text, "\r\n"));
if (!StorageUtility.AppendFile(path, bytes))
{
var msg = "写入日志文件失败。";
if (withConsole) ToConsole(msg);
return msg;
}
}
return null;
}
/// <summary>获取日志文件路径发生错误时返回 NULL 值。</summary>
/// <remarks>d:\app\log\2020-02-02.log</remarks>
/// <remarks>d:\www\app_data\log\2020-02-02.log</remarks>
public static string GetFileDir()
{
// 找到 App_Data 目录。
var appDir = KernelUtility.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");
StorageUtility.CreateFile(filePath, 0, false);
if (!StorageUtility.FileExists(filePath)) return null;
// 返回 log 文件路径。
return filePath;
}
/// <summary>使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。</summary>
public static void Write(params object[] content)
{
var console = Default.UseConsole;
var file = Default.UseFile;
if (console || file)
{
var text = MergeText(true, null, content);
if (console) ToConsole(text);
if (file) ToFile(text);
}
}
#endregion
}
}