[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 => $"""
============={CategoryName}======================
日期:{DT}
{Message}
""";
public bool IsVisible => !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<LogItem> writer) : ILogger
{
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;
public bool IsEnabled(LogLevel logLevel) => logLevel > LogLevel.Debug;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, System.Exception? exception, Func<TState, System.Exception?, string> 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<string, SqliteLogger> _loggers = new(StringComparer.OrdinalIgnoreCase);
private readonly CancellationTokenSource _Cts = new();
private readonly Channel<LogItem> _Queue;
private readonly ChannelWriter<LogItem> _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<LogItem>(option);
_Writer = _Queue.Writer;
var reader = _Queue.Reader;
Task.Run(async () =>
{
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) =>
_loggers.GetOrAdd(categoryName, name => 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<SomeClass> logger)
{
}
public void SomeMethod()
{
var logger=Handler.GetRequiredService<ILogger<T>>();
或者
Handler.GetRequiredService<ILoggerFactory>().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]