Skip to content

日志记录

日志是项目中非常重要的工具,日志的记录决定了后续问题追踪的难度。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中的字段信息如下:

字段类型说明
TraceIdstring一次前端请求,都是同一个TraceId,根据TraceId可以得到本次请求所对应的所有日志
IsRootbool是否是日志起点,请求被发起的第一个日志
CurrentTracestring调用路径,标识着调用过程中所经过的每一个方法,格式是 Method1->Method2->Method3
ServiceNamestring本次日志对应的服务名称(一般是接口的名称)
ClassNamestring本次日志对应的实例类型(实际执行的实例类型)
MethodNamestring本次日志对应的方法名称
ParametersArray调用方法使用的参数
Resultobject调用此方法得到的返回结果(无异常)
ExceptionException调用此方法抛出的异常
Messagestring用户自定义的日志(SurroundLog所执行的日志)
LogLevelenum日志级别(包括Normal、Error、Timeout等)
Timeoutint判定Timeout的时长(单位毫秒,默认是5分钟,用户可自定义)
StartTimeDatetime执行当前方法的开始时间
EndTimeDatetime执行当前方法的结束时间
Durationdouble执行当前方法的耗时(单位毫秒)
ExtraInfoDictionary日志需要记录其它信息(由用户自定义,比如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();
        }
    }
}

沪ICP备2025119739号