大圣又归来 發表於 2026-3-9 15:14:00

[Maui] 造轮子——LoggerSqlite

<p>上文异常处理用到了日志记录器,本文介绍一下基于Sqlite的日志记录器</p>
<p><strong>一、定义一个传递、保存日志的类</strong></p>
<pre><code>public class LogItem
{
    public int Id { get; set; }
    public string DT { get; set; } = default!;
    public LogLevel Level { get; set; }
    public string CategoryName { get; set; } = default!;
    public string Message { get; set; } = default!;
    public string Trace { get; set; } = default!;
    public Exception? Exception { get; set; }
    public string Details =&gt; $"""
      ============={CategoryName}======================
      日期:{DT}
      {Message}
      """;


    public bool IsVisible =&gt; !string.IsNullOrEmpty(Trace);

    public LogItem() { }
    public LogItem(LogLevel level, string categoryName, string message, Exception? e)
    {
      DT = DateTime.Now.ToString();
      Level = level;
      CategoryName = categoryName;
      Exception = e;
    }

}
</code></pre>
<p><strong>二、自定义日志记录器</strong></p>
<pre><code>internal class SqliteLogger(string name, ChannelWriter&lt;LogItem&gt; writer) : ILogger
{
    public IDisposable? BeginScope&lt;TState&gt;(TState state) where TState : notnull =&gt; default!;
    public bool IsEnabled(LogLevel logLevel) =&gt; logLevel &gt; LogLevel.Debug;
    public void Log&lt;TState&gt;(LogLevel logLevel, EventId eventId, TState state, System.Exception? exception, Func&lt;TState, System.Exception?, string&gt; formatter)
    {
      if (!IsEnabled(logLevel))
      {
#if DEBUG
            Console.WriteLine(logLevel + ":" + name + "," + formatter(state, exception));
#endif
            return;
      }
      writer.TryWrite(new LogItem(logLevel, name, formatter(state, exception), exception));
    }
}
</code></pre>
<p><strong>三、自定义日志记录器提供程序</strong></p>
<pre><code>internal class SqliteLoggerProvider : ILoggerProvider
{
    private readonly ConcurrentDictionary&lt;string, SqliteLogger&gt; _loggers = new(StringComparer.OrdinalIgnoreCase);
    private readonly CancellationTokenSource _Cts = new();
    private readonly Channel&lt;LogItem&gt; _Queue;
    private readonly ChannelWriter&lt;LogItem&gt; _Writer;
    private readonly string _DBConnectionString;
    public SqliteLoggerProvider(IConfiguration config)
    {
      var dbName = config["LogDatabaseName"];
      _DBConnectionString = @$"Data Source={dbName}";
      var dbPath = Path.GetDirectoryName(dbName)!;
      if (!Directory.Exists(dbPath))
            Directory.CreateDirectory(dbPath);

      var option = new UnboundedChannelOptions
      {
            SingleReader = true
      };
      _Queue = Channel.CreateUnbounded&lt;LogItem&gt;(option);
      _Writer = _Queue.Writer;
      var reader = _Queue.Reader;


      Task.Run(async () =&gt;
      {
            var cn = new SqliteConnection(_DBConnectionString);
            var cmd = cn.CreateCommand();
            cmd.CommandText = """
            create table if not exists LogItem (
                Id INTEGER PRIMARY KEYAUTOINCREMENT not null,
                DT TEXT not null,
                Level int not null,
                CategoryName nvarchar(128) not null,
                Message TEXT not null,
                Trace TEXT not null
            )
            """;
            await cn.OpenAsync();
            await cmd.ExecuteNonQueryAsync();
            await cn.CloseAsync();


            cmd.CommandText = $"""
            insert into {nameof(LogItem)}(
                {nameof(LogItem.DT)},
                {nameof(LogItem.Level)},
                {nameof(LogItem.CategoryName)},
                {nameof(LogItem.Message)},
                {nameof(LogItem.Trace)}
                )
            values(@P0,@P1,@P2,@P3,@P4)
            """;
            cmd.Parameters.AddWithValue("@P0", null);
            cmd.Parameters.AddWithValue("@P1", null);
            cmd.Parameters.AddWithValue("@P2", null);
            cmd.Parameters.AddWithValue("@P3", null);
            cmd.Parameters.AddWithValue("@P4", null);

            try
            {
                while (await reader.WaitToReadAsync(_Cts.Token))
                {
                  await cn.OpenAsync();
                  while (reader.TryRead(out var item))
                  {
                        if (item.Exception is null)
                        {
                            item.Trace = "";
                        }
                        else
                        {

                            item.Message = (string.IsNullOrEmpty(item.Message) ? "" : (item.Message + "\r\n")) + item.Exception.GetFriendMessage();
                            item.Trace = System.Text.Json.JsonSerializer.Serialize(VMTrace.CreateList(item.Exception.StackTrace));
                        }

                        cmd.Parameters.Value = DateTime.Now;
                        cmd.Parameters.Value = (int)item.Level;
                        cmd.Parameters.Value = item.CategoryName;
                        cmd.Parameters.Value = item.Message;
                        cmd.Parameters.Value = item.Trace;
                        await cmd.ExecuteNonQueryAsync();
                  }
                  await cn.CloseAsync();
                }
            }
            catch (System.Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
      });
    }
    public ILogger CreateLogger(string categoryName) =&gt;
      _loggers.GetOrAdd(categoryName, name =&gt; new SqliteLogger(name, _Writer));
    public void Dispose()
    {
      _Cts.Cancel();
      _loggers.Clear();
    }
}

</code></pre>
<p>这个提供程序顺便把日志写入数据库的活给干了。</p>
<p>这里表扬一下Channel这个东东,它的Reader比较好,一直读到队列为空,然后等待,实在是比较窝心。</p>
<p><strong>四、总结一下</strong></p>
<ol>
<li>
<p>日志的消费者(领导)</p>
<ul>
<li>给我弄个记录器<pre><code>public class SomeClass(ILogger&lt;SomeClass&gt; logger)
{
}

public void SomeMethod()
{
    var logger=Handler.GetRequiredService&lt;ILogger&lt;T&gt;&gt;();
    或者
    Handler.GetRequiredService&lt;ILoggerFactory&gt;().CreateLogger("CategogyName");
}
</code></pre>
</li>
<li>我要写点日志</li>
</ul>
</li>
<li>
<p>日志工厂ILoggerFactory (厂长)<br>
厂长一通忙乎,催着手下的ILoggerProvider</p>
<ul>
<li>快快快,整个记录器,快快快领导要讲话了,是什么类型的</li>
<li>快快快,领导讲的话记下来!!!</li>
</ul>
</li>
<li>
<p>日志供应商ILoggerProvider(车间主任)</p>
<ul>
<li>给你一个记录器,就是领导要的类型的</li>
</ul>
</li>
<li>
<p>日志记录器ILogger(产品)</p>
<ul>
<li>我就记一下,我就记这一类的,其他事别烦我</li>
</ul>
</li>
</ol><br><br>
来源:https://www.cnblogs.com/catzhou/p/19690278
頁: [1]
查看完整版本: [Maui] 造轮子——LoggerSqlite