- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
在应用程序中,日志记录是一个至关重要的功能。不仅有助于调试和监控应用程序,还能帮助我们了解应用程序的运行状态。 在这个示例中将展示如何实现一个自定义的日志记录器,先说明一下,这个实现和Microsoft.Extensions.Logging、Serilog、NLog什么的无关,这里只是将自定义的日志数据存入数据库中,或许你也可以理解为实现的是一个存数据的“Repository”,只不过用这个Repository来存的是日志🙈。这个实现包含一个抽象包和两个实现包,两个实现分别是用 EntityFramework Core 和 MySqlConnector 。日志记录操作将放在本地队列中异步处理,以确保不影响业务处理.
首先,我们需要定义一个日志记录接口 ICustomLogger,它包含两个方法:LogReceived 和 LogProcessed。LogReceived 用于记录接收到的日志,LogProcessed 用于更新日志的处理状态.
namespace Logging.Abstractions;
public interface ICustomLogger
{
/// <summary>
/// 记录一条日志
/// </summary>
void LogReceived(CustomLogEntry logEntry);
/// <summary>
/// 根据Id更新这条日志
/// </summary>
void LogProcessed(string logId, bool isSuccess);
}
定义一个日志结构实体CustomLogEntry,用于存储日志的详细信息:
namespace Logging.Abstractions;
public class CustomLogEntry
{
/// <summary>
/// 日志唯一Id,数据库主键
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Message { get; set; } = default!;
public bool IsSuccess { get; set; }
public DateTime CreateTime { get; set; } = DateTime.UtcNow;
public DateTime? UpdateTime { get; set; } = DateTime.UtcNow;
}
接下来,定义一个抽象类CustomLogger,它实现了ICustomLogger接口,并提供了日志记录的基本功能,将日志写入操作(插入or更新)放在本地队列总异步处理。使用ConcurrentQueue来确保线程安全,并开启一个后台任务异步处理这些日志。这个抽象类只负责将日志写入命令放到队列中,实现类负责消费队列中的消息,确定日志应该怎么写?往哪里写?这个示例中后边会有两个实现,一个是基于EntityFramework Core的实现,另一个是MySqlConnector的实现.
封装一下日志写入命令 。
namespace Logging.Abstractions;
public class WriteCommand(WriteCommandType commandType, CustomLogEntry logEntry)
{
public WriteCommandType CommandType { get; } = commandType;
public CustomLogEntry LogEntry { get; } = logEntry;
}
public enum WriteCommandType
{
/// <summary>
/// 插入
/// </summary>
Insert,
/// <summary>
/// 更新
/// </summary>
Update
}
CustomLogger实现 。
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
namespace Logging.Abstractions;
public abstract class CustomLogger : ICustomLogger, IDisposable, IAsyncDisposable
{
protected ILogger<CustomLogger> Logger { get; }
protected ConcurrentQueue<WriteCommand> WriteQueue { get; }
protected Task WriteTask { get; }
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly CancellationToken _cancellationToken;
protected CustomLogger(ILogger<CustomLogger> logger)
{
Logger = logger;
WriteQueue = new ConcurrentQueue<WriteCommand>();
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
WriteTask = Task.Factory.StartNew(TryWriteAsync, _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
public void LogReceived(CustomLogEntry logEntry)
{
WriteQueue.Enqueue(new WriteCommand(WriteCommandType.Insert, logEntry));
}
public void LogProcessed(string messageId, bool isSuccess)
{
var logEntry = GetById(messageId);
if (logEntry == null)
{
return;
}
logEntry.IsSuccess = isSuccess;
logEntry.UpdateTime = DateTime.UtcNow;
WriteQueue.Enqueue(new WriteCommand(WriteCommandType.Update, logEntry));
}
private async Task TryWriteAsync()
{
try
{
while (!_cancellationToken.IsCancellationRequested)
{
if (WriteQueue.IsEmpty)
{
await Task.Delay(1000, _cancellationToken);
continue;
}
if (WriteQueue.TryDequeue(out var writeCommand))
{
await WriteAsync(writeCommand);
}
}
while (WriteQueue.TryDequeue(out var remainingCommand))
{
await WriteAsync(remainingCommand);
}
}
catch (OperationCanceledException)
{
// 任务被取消,正常退出
}
catch (Exception e)
{
Logger.LogError(e, "处理待写入日志队列异常");
}
}
protected abstract CustomLogEntry? GetById(string messageId);
protected abstract Task WriteAsync(WriteCommand writeCommand);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore();
Dispose(false);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_cancellationTokenSource.Cancel();
try
{
WriteTask.Wait();
}
catch (AggregateException ex)
{
foreach (var innerException in ex.InnerExceptions)
{
Logger.LogError(innerException, "释放资源异常");
}
}
finally
{
_cancellationTokenSource.Dispose();
}
}
}
protected virtual async Task DisposeAsyncCore()
{
_cancellationTokenSource.Cancel();
try
{
await WriteTask;
}
catch (Exception e)
{
Logger.LogError(e, "释放资源异常");
}
finally
{
_cancellationTokenSource.Dispose();
}
}
}
为了方便表结构迁移,我们可以使用FluentMigrator.Runner.MySql,在项目中引入:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentMigrator.Runner.MySql" Version="6.2.0" />
</ItemGroup>
</Project>
新建一个CreateLogEntriesTable,放在Migrations目录下 。
[Migration(20241216)]
public class CreateLogEntriesTable : Migration
{
public override void Up()
{
Create.Table("LogEntries")
.WithColumn("Id").AsString(36).PrimaryKey()
.WithColumn("Message").AsCustom(text)
.WithColumn("IsSuccess").AsBoolean().NotNullable()
.WithColumn("CreateTime").AsDateTime().NotNullable()
.WithColumn("UpdateTime").AsDateTime();
}
public override void Down()
{
Delete.Table("LogEntries");
}
}
添加服务注册 。
using FluentMigrator.Runner;
using Logging.Abstractions;
using Logging.Abstractions.Migrations;
namespace Microsoft.Extensions.DependencyInjection;
public static class CustomLoggerExtensions
{
/// <summary>
/// 添加自定义日志服务表结构迁移
/// </summary>
/// <param name="services"></param>
/// <param name="connectionString">数据库连接字符串</param>
/// <returns></returns>
public static IServiceCollection AddCustomLoggerMigration(this IServiceCollection services, string connectionString)
{
services.AddFluentMigratorCore()
.ConfigureRunner(
rb => rb.AddMySql5()
.WithGlobalConnectionString(connectionString)
.ScanIn(typeof(CreateLogEntriesTable).Assembly)
.For.Migrations()
)
.AddLogging(lb =>
{
lb.AddFluentMigratorConsole();
});
using var serviceProvider = services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var runner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
runner.MigrateUp();
return services;
}
}
新建Logging.EntityFrameworkCore项目,添加对Logging.Abstractions项目的引用,并在项目中安装Pomelo.EntityFrameworkCore.MySql和Microsoft.Extensions.ObjectPool.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.11" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Logging.Abstractions\Logging.Abstractions.csproj" />
</ItemGroup>
</Project>
创建CustomLoggerDbContext类,用于管理日志实体 。
using Logging.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace Logging.EntityFrameworkCore;
public class CustomLoggerDbContext(DbContextOptions<CustomLoggerDbContext> options) : DbContext(options)
{
public virtual DbSet<CustomLogEntry> LogEntries { get; set; }
}
使用 ObjectPool 管理 DbContext:提高性能,减少 DbContext 的创建和销毁开销。 创建CustomLoggerDbContextPoolPolicy 。
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.ObjectPool;
namespace Logging.EntityFrameworkCore;
/// <summary>
/// DbContext 池策略
/// </summary>
/// <param name="options"></param>
public class CustomLoggerDbContextPoolPolicy(DbContextOptions<CustomLoggerDbContext> options) : IPooledObjectPolicy<CustomLoggerDbContext>
{
/// <summary>
/// 创建 DbContext
/// </summary>
/// <returns></returns>
public CustomLoggerDbContext Create()
{
return new CustomLoggerDbContext(options);
}
/// <summary>
/// 回收 DbContext
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public bool Return(CustomLoggerDbContext context)
{
// 重置 DbContext 状态
context.ChangeTracker.Clear();
return true;
}
}
创建一个EfCoreCustomLogger,继承自CustomLogger,实现日志写入的具体逻辑 。
using Logging.Abstractions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
namespace Logging.EntityFrameworkCore;
/// <summary>
/// EfCore自定义日志记录器
/// </summary>
public class EfCoreCustomLogger(ObjectPool<CustomLoggerDbContext> contextPool, ILogger<EfCoreCustomLogger> logger) : CustomLogger(logger)
{
/// <summary>
/// 根据Id查询日志
/// </summary>
/// <param name="logId"></param>
/// <returns></returns>
protected override CustomLogEntry? GetById(string logId)
{
var dbContext = contextPool.Get();
try
{
return dbContext.LogEntries.Find(logId);
}
finally
{
contextPool.Return(dbContext);
}
}
/// <summary>
/// 写入日志
/// </summary>
/// <param name="writeCommand"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
protected override async Task WriteAsync(WriteCommand writeCommand)
{
var dbContext = contextPool.Get();
try
{
switch (writeCommand.CommandType)
{
case WriteCommandType.Insert:
if (writeCommand.LogEntry != null)
{
await dbContext.LogEntries.AddAsync(writeCommand.LogEntry);
}
break;
case WriteCommandType.Update:
{
if (writeCommand.LogEntry != null)
{
dbContext.LogEntries.Update(writeCommand.LogEntry);
}
break;
}
default:
throw new ArgumentOutOfRangeException();
}
await dbContext.SaveChangesAsync();
}
finally
{
contextPool.Return(dbContext);
}
}
}
添加服务注册 。
using Logging.Abstractions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
namespace Logging.EntityFrameworkCore;
public static class EfCoreCustomLoggerExtensions
{
public static IServiceCollection AddEfCoreCustomLogger(this IServiceCollection services, string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentNullException(nameof(connectionString));
}
services.AddCustomLoggerMigration(connectionString);
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
services.AddSingleton(serviceProvider =>
{
var options = new DbContextOptionsBuilder<CustomLoggerDbContext>()
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
.Options;
var poolProvider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
return poolProvider.Create(new CustomLoggerDbContextPoolPolicy(options));
});
services.AddSingleton<ICustomLogger, EfCoreCustomLogger>();
return services;
}
}
MySqlConnector 的实现比较简单,利用原生SQL操作数据库完成日志的插入和更新。 新建Logging.Connector项目,添加对Logging.Abstractions项目的引用,并安装MySqlConnector包 。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MySqlConnector" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Logging.Abstractions\Logging.Abstractions.csproj" />
</ItemGroup>
</Project>
为了方便维护,我们把需要用到的SQL脚本放在一个Consts类中 。
namespace Logging.MySqlConnector;
public class Consts
{
/// <summary>
/// 插入日志
/// </summary>
public const string InsertSql = """
INSERT INTO `LogEntries` (`Id`, `TranceId`, `BizType`, `Body`, `Component`, `MsgType`, `Status`, `CreateTime`, `UpdateTime`, `Remark`)
VALUES (@Id, @TranceId, @BizType, @Body, @Component, @MsgType, @Status, @CreateTime, @UpdateTime, @Remark);
""";
/// <summary>
/// 更新日志
/// </summary>
public const string UpdateSql = """
UPDATE `LogEntries` SET `Status` = @Status, `UpdateTime` = @UpdateTime
WHERE `Id` = @Id;
""";
/// <summary>
/// 根据Id查询日志
/// </summary>
public const string QueryByIdSql = """
SELECT `Id`, `TranceId`, `BizType`, `Body`, `Component`, `MsgType`, `Status`, `CreateTime`, `UpdateTime`, `Remark`
FROM `LogEntries`
WHERE `Id` = @Id;
""";
}
创建MySqlConnectorCustomLogger类,实现日志写入的具体逻辑 。
using Logging.Abstractions;
using Microsoft.Extensions.Logging;
using MySqlConnector;
namespace Logging.MySqlConnector;
/// <summary>
/// 使用 MySqlConnector 实现记录日志
/// </summary>
public class MySqlConnectorCustomLogger : CustomLogger
{
/// <summary>
/// 数据库连接字符串
/// </summary>
private readonly string _connectionString;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="connectionString">MySQL连接字符串</param>
/// <param name="logger"></param>
public MySqlConnectorCustomLogger(
string connectionString,
ILogger<MySqlConnectorCustomLogger> logger)
: base(logger)
{
_connectionString = connectionString;
}
/// <summary>
/// 根据Id查询日志
/// </summary>
/// <param name="messageId"></param>
/// <returns></returns>
protected override CustomLogEntry? GetById(string messageId)
{
using var connection = new MySqlConnection(_connectionString);
connection.Open();
using var command = new MySqlCommand(Consts.QueryByIdSql, connection);
command.Parameters.AddWithValue("@Id", messageId);
using var reader = command.ExecuteReader();
if (!reader.Read())
{
return null;
}
return new CustomLogEntry
{
Id = reader.GetString(0),
Message = reader.GetString(1),
IsSuccess = reader.GetBoolean(2),
CreateTime = reader.GetDateTime(3),
UpdateTime = reader.GetDateTime(4)
};
}
/// <summary>
/// 处理日志
/// </summary>
/// <param name="writeCommand"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
protected override async Task WriteAsync(WriteCommand writeCommand)
{
await using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
switch (writeCommand.CommandType)
{
case WriteCommandType.Insert:
{
if (writeCommand.LogEntry != null)
{
await using var command = new MySqlCommand(Consts.InsertSql, connection);
command.Parameters.AddWithValue("@Id", writeCommand.LogEntry.Id);
command.Parameters.AddWithValue("@Message", writeCommand.LogEntry.Message);
command.Parameters.AddWithValue("@IsSuccess", writeCommand.LogEntry.IsSuccess);
command.Parameters.AddWithValue("@CreateTime", writeCommand.LogEntry.CreateTime);
command.Parameters.AddWithValue("@UpdateTime", writeCommand.LogEntry.UpdateTime);
await command.ExecuteNonQueryAsync();
}
break;
}
case WriteCommandType.Update:
{
if (writeCommand.LogEntry != null)
{
await using var command = new MySqlCommand(Consts.UpdateSql, connection);
command.Parameters.AddWithValue("@Id", writeCommand.LogEntry.Id);
command.Parameters.AddWithValue("@IsSuccess", writeCommand.LogEntry.IsSuccess);
command.Parameters.AddWithValue("@UpdateTime", writeCommand.LogEntry.UpdateTime);
await command.ExecuteNonQueryAsync();
}
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}
添加服务注册 。
using Logging.Abstractions;
using Logging.MySqlConnector;
using Microsoft.Extensions.Logging;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// MySqlConnector 日志记录器扩展
/// </summary>
public static class MySqlConnectorCustomLoggerExtensions
{
/// <summary>
/// 添加 MySqlConnector 日志记录器
/// </summary>
/// <param name="services"></param>
/// <param name="connectionString"></param>
/// <returns></returns>
public static IServiceCollection AddMySqlConnectorCustomLogger(this IServiceCollection services, string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentNullException(nameof(connectionString));
}
services.AddSingleton<ICustomLogger>(s =>
{
var logger = s.GetRequiredService<ILogger<MySqlConnectorCustomLogger>>();
return new MySqlConnectorCustomLogger(connectionString, logger);
});
services.AddCustomLoggerMigration(connectionString);
return services;
}
}
下边是一个EntityFramework Core的实现使用示例,MySqlConnector的使用方式相同。 新建WebApi项目,添加Logging.ntityFrameworkCore 。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 添加EntityFrameworkCore日志记录器
var connectionString = builder.Configuration.GetConnectionString("MySql");
builder.Services.AddEfCoreCustomLogger(connectionString!);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
在控制器中使用 。
namespace EntityFrameworkCoreTest.Controllers;
[ApiController]
[Route("[controller]")]
public class TestController(ICustomLogger customLogger) : ControllerBase
{
[HttpPost("InsertLog")]
public IActionResult Post(CustomLogEntry model)
{
customLogger.LogReceived(model);
return Ok();
}
[HttpPut("UpdateLog")]
public IActionResult Put(string messageId, MessageStatus status)
{
customLogger.LogProcessed(messageId, status);
return Ok();
}
}
最后此篇关于使用.NETCore实现一个自定义日志记录器的文章就讲到这里了,如果你想了解更多关于使用.NETCore实现一个自定义日志记录器的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
好的,所以我编辑了以下... 只需将以下内容放入我的 custom.css #rt-utility .rt-block {CODE HERE} 但是当我尝试改变... 与 #rt-sideslid
在表格 View 中,我有一个自定义单元格(在界面生成器中高度为 500)。在该单元格中,我有一个 Collection View ,我按 (10,10,10,10) 固定到边缘。但是在 tablev
对于我的无能,我很抱歉,但总的来说,我对 Cocoa、Swift 和面向对象编程还很陌生。我的主要来源是《Cocoa Programming for OS X》(第 5 版),以及 Apple 的充满
我正在使用 meta-tegra 为我的 NVIDIA Jetson Nano 构建自定义图像。我需要 PyTorch,但没有它的配方。我在设备上构建了 PyTorch,并将其打包到设备上的轮子中。现
在 jquery 中使用 $.POST 和 $.GET 时,有没有办法将自定义变量添加到 URL 并发送它们?我尝试了以下方法: $.ajax({type:"POST", url:"file.php?
Traefik 已经默认实现了很多中间件,可以满足大部分我们日常的需求,但是在实际工作中,用户仍然还是有自定义中间件的需求,为解决这个问题,官方推出了一个 Traefik Pilot[1] 的功
我想让我的 CustomTextInputLayout 将 Widget.MaterialComponents.TextInputLayout.OutlinedBox 作为默认样式,无需在 XML 中
我在 ~/.emacs 中有以下自定义函数: (defun xi-rgrep (term) (grep-compute-defaults) (interactive "sSearch Te
我有下表: 考虑到每个月的权重,我的目标是在 5 个月内分散 10,000 个单位。与 10,000 相邻的行是我最好的尝试(我在这上面花了几个小时)。黄色是我所追求的。 我试图用来计算的逻辑如下:计
我的表单中有一个字段,它是文件类型。当用户点击保存图标时,我想自然地将文件上传到服务器并将文件名保存在数据库中。我尝试通过回显文件名来测试它,但它似乎不起作用。另外,如何将文件名添加到数据库中?是在模
我有一个 python 脚本来发送电子邮件,它工作得很好,但问题是当我检查我的电子邮件收件箱时。 我希望该用户名是自定义用户名,而不是整个电子邮件地址。 最佳答案 发件人地址应该使用的格式是: You
我想减小 ggcorrplot 中标记的大小,并减少文本和绘图之间的空间。 library(ggcorrplot) data(mtcars) corr <- round(cor(mtcars), 1)
GTK+ noob 问题在这里: 是否可以自定义 GtkFileChooserButton 或 GtkFileChooserDialog 以删除“位置”部分(左侧)和顶部的“位置”输入框? 我实际上要
我正在尝试在主页上使用 ajax 在 magento 中使用 ajax 显示流行的产品列表,我可以为 5 或“N”个产品执行此操作,但我想要的是将分页工具栏与结果集一起添加. 这是我添加的以显示流行产
我正在尝试使用 PasswordResetForm 内置函数。 由于我想要自定义表单字段,因此我编写了自己的表单: class FpasswordForm(PasswordResetForm):
据我了解,新的 Angular 7 提供了拖放功能。我搜索了有关 DnD 的 Tree 组件,但没有找到与树相关的内容。 我在 Stackblitz 上找到的一个工作示例.对比drag'ndrop功能
我必须开发一个自定义选项卡控件并决定使用 WPF/XAML 创建它,因为我无论如何都打算学习它。完成后应该是这样的: 到目前为止,我取得了很好的进展,但还有两个问题: 只有第一个/最后一个标签项应该有
我要定制xtable用于导出到 LaTeX。我知道有些问题是关于 xtable在这里,但我找不到我要找的具体东西。 以下是我的表的外观示例: my.table <- data.frame(Specif
用ejs在这里显示日期 它给我结果 Tue Feb 02 2016 16:02:24 GMT+0530 (IST) 但是我需要表现为 19th January, 2016 如何在ejs中执行此操作?
我想问在 JavaFX 中使用自定义对象制作 ListView 的最佳方法,我想要一个每个项目如下所示的列表: 我搜了一下,发现大部分人都是用细胞工厂的方法来做的。有没有其他办法?例如使用客户 fxm
我是一名优秀的程序员,十分优秀!