Appearance
日志记录
日志是项目中非常重要的工具,日志的记录决定了后续问题追踪的难度。AsDI接供日志组件,此时需要用到AsDI.Logger,可以通过NuGet进行安装
sh
dotnet add package AsDI.Logger --version 2.0.0安装入项目以后,日志默认通过控制台输出执行日志,所有面向接口编程且由AsDI管理的服务,都会自动输出日志,默认在控制台输出
基本示例
例如,编写如下代码:
C#
[Include]
public class Program
{
[AutoAssemble]
public static ITest test;
public static void Main(string[] args)
{
AsDIContext.Start();
test.f();
}
}
public interface ITest
{
void f();
}
[Service]
public class Test : ITest
{
public void f()
{
Console.WriteLine("test");
}
}此时,运行此程序,将会输出:
sh
>[2025-03-02 09:32:40.015][Unknown][F29F0131D51F44DEAED362856ED4237C]Begin Execute : Test.f
>test
>[2025-03-02 09:32:40.022][ Normal][F29F0131D51F44DEAED362856ED4237C]Executed(cost 6.7864ms):Test.f自定义日志输出方式
日志的默认输出方式是控制台输出,这可能无法满足实际的需要,大多数情况下,长期运维的系统,日志记录会在文件或数据库以方便返查追溯。 需要注意的是,日志的输出是同步的,为了不影响系统性能,在自定义日志输出方式时,可以将日志输出方式改成异步。
自定义日志输出方式,需要继承ILogWriter接口,并标注 [Service(2)], 以下是自定义日志的示例:
C#
[Service(2)]
public class LogWriter : ILogWriter
{
//是否异步记录日志(如果希望日志记录,不要影响业务执行,可以将日志执行改成异步)
public bool Async => true;
private FileWriter FileWriter = new FileWriter();
public void Begin(LogInfo logInfo)
{
WriteToFile(logInfo);
}
public void End(LogInfo logInfo)
{
WriteToFile(logInfo);
}
private void WriteToFile(LogInfo logInfo)
{
FileWriter.WriteLog("d:\\logger\\log.txt", logInfo.ToString());
}
}此时,运行此程序,可以在log.txt文件中,得到如下输出:
sh
[7110E85DF82F417FA49AB00442D68C2D][Unknown][2025-03-02 11:29:26.452]Test.f Executing :
[7110E85DF82F417FA49AB00442D68C2D][ Normal][2025-03-02 11:29:26.459]Test.f Executed(cost 6.8125ms):当然,以上的日志输出,都是最基本的信息,如果需要输出诸如参数、结果等信息,可以自行序列化LogInfo中的Parameter、Result等,LogInfo中的字段信息如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| TraceId | string | 一次前端请求,都是同一个TraceId,根据TraceId可以得到本次请求所对应的所有日志 |
| IsRoot | bool | 是否是日志起点,请求被发起的第一个日志 |
| CurrentTrace | string | 调用路径,标识着调用过程中所经过的每一个方法,格式是 Method1->Method2->Method3 |
| ServiceName | string | 本次日志对应的服务名称(一般是接口的名称) |
| ClassName | string | 本次日志对应的实例类型(实际执行的实例类型) |
| MethodName | string | 本次日志对应的方法名称 |
| Parameters | Array | 调用方法使用的参数 |
| Result | object | 调用此方法得到的返回结果(无异常) |
| Exception | Exception | 调用此方法抛出的异常 |
| Message | string | 用户自定义的日志(SurroundLog所执行的日志) |
| LogLevel | enum | 日志级别(包括Normal、Error、Timeout等) |
| Timeout | int | 判定Timeout的时长(单位毫秒,默认是5分钟,用户可自定义) |
| StartTime | Datetime | 执行当前方法的开始时间 |
| EndTime | Datetime | 执行当前方法的结束时间 |
| Duration | double | 执行当前方法的耗时(单位毫秒) |
| ExtraInfo | Dictionary | 日志需要记录其它信息(由用户自定义,比如Token、浏览器、IP等信息) |
方法内部的日志
如果想在方法内部记录日志,可以使用SurroundLog类型,一般用于在无法切面的类型和需要具体定位问题的地方,有助于记录更加详细的日志。 以下是SurroundLog的用法示例:
C#
[Service]
public class Test : ITest
{
public void f()
{
using(var log=new SurroundLog("Execute complex logic",2000))
{
//do something complex
Thread.Sleep(5000);
Console.WriteLine("test");
}
Console.WriteLine("test");
}
}在复杂逻辑上,加上了相应日志,来检查其执行时长,得到输出:
sh
>[E7FCE75C6E724B92A4EB0BCA30381BAC][Unknown][2025-03-02 15:23:47.023]Test.f Executing :
>[E7FCE75C6E724B92A4EB0BCA30381BAC][Unknown][2025-03-02 15:23:47.027]Test.f->& Executing : Execute complex logic
>[E7FCE75C6E724B92A4EB0BCA30381BAC][Timeout][2025-03-02 15:23:52.030]Test.f->& Executed(cost 5002.1476ms): Execute complex logic
>[E7FCE75C6E724B92A4EB0BCA30381BAC][ Normal][2025-03-02 15:23:52.030]Test.f Executed(cost 5006.6202ms):日志控制
自定义超时时间
默认地,一个方法的执行超时时间是5分钟,如果需要修改全局的超时时间,可以修改Logger.GlobalTimeout,例如:
C#
Logger.GlobalTimeout=5000; //默认一个方法,执行超过5秒,就判断为执行慢,日志记录为执行超时如果只是对某一个类或某一个类的方法,需要可以对方法使用 [Log(timeout)] 进行标注(可以标注在接口方法上,也可以标注在实现类方法上),例如:
C#
public interface ITest
{
[Log(2000)]
void f();
}或者
C#
[Service]
public class Test : ITest
{
[Log(2000)]
public void f()
{
Console.WriteLine("test");
}
}不记录日志
有些类或方法不需要记录日志,可以使用 [NoLog] 进行标注,这样此类或方法将不再记录日志,例如:
C#
[Service]
[NoLog]
public class LogService : ILogService
{
//本服务用于记录日志,所以本身不需要记录日志
public void Log(LogInfo logInfo)
{
//将日志信息写入数据库
}
}需要注意的是,当一个类型或方法不记录日志,其内调用的其它服务也将不再记录日志,如果需要其它服务继续记录日志(不推荐),可以修改NoLog标注,如下:
C#
[Service]
[NoLog(false)]
public class LogService : ILogService
{
}单线程程序
在Windows Forms和Console程序中,如果需要记录日志,因为程序执行都在单个线程中完成,所有的日志的TraceId都会相同,所以需要手动开启一个新的TranceId,以便于知道执行的路径。
例如,在Windows Forms中,如果需要程序在按下按钮时,开启一次新的TraceId,可以如下处理:
C#
private void btnBegin_Click(object sender, EventArgs e)
{
Logger.Begin(); //开启一个新的TraceId
//other business code
}当前,这样一个一个写可能比较麻烦,推荐统一处理。比如按钮被点击、输入框里按了回车、下拉选择选择了新项、 单选和多选的选择进行了变更,都开启新的Trace,如下:
C#
public Form1()
{
InitializeComponent();
foreach (Control item in this.Controls)
{
if (item is Button)
{
item.Click += (sender, e) => Logger.Begin();
}
if (item is TextBox || item is RichTextBox)
{
item.KeyDown += (sender, e) =>
{
if (e.KeyCode == Keys.Enter)
{
Logger.Begin();
}
};
}
if (item is ComboBox cb)
{
cb.SelectedIndexChanged += (sender, e) => Logger.Begin();
}
if (item is CheckBox ck)
{
ck.CheckedChanged += (sender, e) => Logger.Begin();
}
if (item is RadioButton rb)
{
rb.CheckedChanged += (sender, e) => Logger.Begin();
}
}
}