日志记录

日志记录是开发中不可或缺的一部分,它可以帮助我们追踪应用程序的运行状态、调试问题以及记录重要事件。

应用日志

ASP.NET Core 提供了内置的日志记录功能,支持多种日志提供程序。我们将使用OpenTelemetry来实现日志记录。

业务日志

业务日志是根据实际业务需求对特定对象操作时进行记录。

这不同于请求日志的记录,无法进行统一的拦截处理。

如以下需求场景: 用户张三删除了一篇博客《程序员的素养》,需要在日志中记录:张三删除了博客:程序员的素养。

且能够根据用户(张三),操作(删除),模块(博客),对象(程序员的素养)进行日志的筛选。

为了实现此种业务需求,我们提供了实现的方式。

使用后台队列来执行日志记录

通常我们会把业务日志写入库,以便后续的筛选查询。

我们将通过后台队列来执行日志写入操作,以避免阻塞正常业务代码的执行。

SystemMod模块Worker目录下的SystemLogTaskHostedService.cs给出了实现示例。

如若使用请将该服务添加到依赖注入中,如:

services.AddSingleton(typeof(EntityTaskQueue<SystemLogs>));
services.AddSingleton(typeof(SystemLogService));
services.AddHostedService<SystemLogTaskHostedService>();

接下来我们就可以在其他业务代码中通过EntityTaskQueue,将日志对象添加到队列中。

按需记录日志

我们可以在SystemLogs.cs实体中添加一个静态方法,如:

public static SystemLogs NewLog(string userName, Guid userId, string targetName, ActionType actionType, string? route = null, string? description = null)
{
    return new SystemLogs
    {
        SystemUserId = userId,
        ActionUserName = userName,
        TargetName = targetName,
        Route = route ?? string.Empty,
        ActionType = actionType,
        Description = description,
    };
}

在Manager中添加日志记录方法

我们可以在Manager中封装日志记录的方法,以便在控制器中调用,如记录登录日志:

public async Task SaveLoginLogAsync(SystemUser user, string description)
{
    var log = SystemLogs.NewLog(user.UserName, user.Id, "登录", ActionType.Login, description: description);
    await _taskQueue.AddItemAsync(log);
}

封装业务日志服务

我们可以将业务日志封装成服务,以便在其他地方使用。可查看SystemMod中的SystemLogService

using Ater.Web.Extension;
namespace SystemMod;
/// <summary>
/// 业务日志服务
/// </summary>
public class SystemLogService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly EntityTaskQueue<SystemLogs> _taskQueue;
    private readonly IUserContext _context;
    /// <summary>
    /// 
    /// </summary>
    /// <param name="serviceProvider"></param>
    /// <param name="taskQueue"></param>
    public SystemLogService(IServiceProvider serviceProvider, EntityTaskQueue<SystemLogs> taskQueue)
    {
        _serviceProvider = serviceProvider;
        _taskQueue = taskQueue;
        _context = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IUserContext>();
    }
    /// <summary>
    /// 记录日志,优先从UserContext中获取用户信息
    /// 匿名用户访问,需要传入userName和userId
    /// </summary>
    /// <param name="targetName"></param>
    /// <param name="actionType"></param>
    /// <param name="description"></param>
    /// <param name="userName"></param>
    /// <param name="userId"></param>
    /// <returns></returns>
    public async Task NewLog(string targetName, ActionType actionType, string description, string? userName = null, Guid? userId = null)
    {
        userId = _context.UserId == Guid.Empty ? userId : _context.UserId;
        userName = string.IsNullOrEmpty(_context.Username) ? userName : _context.Username;
        var route = _context!.GetHttpContext()?.Request.Path.Value;
        if (userId == null || userId.Equals(Guid.Empty))
        {
            return;
        }
        var log = SystemLogs.NewLog(userName ?? "", userId.Value, targetName, actionType, route, description);
        await _taskQueue.AddItemAsync(log);
    }
}

然后你可以在其他控制器或Manager中使用该服务记录业务日志。

Tip

以上示例可在SystemMod中找到相关实现,你也可参考该实现在其他模块或Http.API中实现自己的日志记录服务。