65.9K
CodeProject 正在变化。 阅读更多。
Home

在 ASP.NET Core 中记录当前用户的两种方法

starIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

1.00/5 (5投票s)

2021 年 7 月 2 日

CPOL

3分钟阅读

viewsIcon

8699

如何在 ASP.NET Core 中自动将当前用户信息添加到日志中

首先在 ASP.NET Boilerplate 中与 log4net 一起展示。 其次,在 ABP Framework 中与 Microsoft Logging Framework 和 Serilog 一起展示。

如果您曾经需要调试生产系统的问题,您就会知道良好的日志记录至关重要。 然而,知道哪些信息可能有用,以及多少才算太多,几乎是一种艺术形式。

我最近有机会发展这种艺术形式,当时我发布了一个基于 ASP.NET Boilerplate 的系统到生产环境中。 总的来说,部署进行得很顺利,但我意识到我未能将当前登录的用户或租户包含在日志中。 这使得调试问题变得更加困难。

因此,这就是如何在 ASP.NET Core 中添加有关当前登录用户的信息的故事,可能包括在多租户解决方案中的租户。 首先,我将使用 log4net 在 ASP.NET Boilerplate 中展示它。 接下来,我将展示如何使用 ABP Framework 和通过 Serilog 的 Microsoft Logging Framework。 希望您能在这里找到一些适合您的技术堆栈的东西,并帮助您培养您的内在日志艺术。

ASP.NET Boilerplate + log4net

对于 log4net,第一个技巧是添加自定义字段。 这发生在 log4net.config 配置文件中。 事实证明,通过 log4net 的 property{name} 语法,这有点不直观

<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender" >
    ...
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%-5level %date [%-5.5property{userid}] 
         [%-5.5property{tenantid}] [%-5.5thread] %-40.40logger - %message%newline" />
</layout>
</appender>

我用方括号括起了 useridtenantid 字段,并使用定宽的 模式布局 -5.5 将小于 5 个字符的整数值填充到 5 个字符。

要在 log4net 中填充这些字段,您需要在 上下文上设置属性,这使得它对所有日志都可用。 有四个上下文可供选择,但此处最有意义的是逻辑线程上下文,因为这是处理请求的级别,并且即使使用不同的线程来恢复请求,它也可以在等待点之间保持持久性。 然后代码看起来像 LogicalThreadContext.Properties["userid"] = ??。 但在哪里设置它呢?

最合适的位置是在请求管道期间的中间件组件中,就在身份验证之后,以便可以使用当前用户。 换句话说

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    ...
    app.UseAuthentication();
    
    app.UseSessionLogging();
    
    ...
}

UseSessionLogging 是一个扩展方法,看起来像这样

public static class SessionLoggingMiddlewareUtil
{
    public static void UseSessionLogging(this IApplicationBuilder app)
    {
        app.UseMiddleware<SessionLoggingMiddleware>();
    }
}

我在 ASP.NET Core 中选择了 基于工厂的中间件激活,以便我可以获得依赖注入,以便能够访问 IAbpSession,从中我可以获取当前用户和租户。 因此,最后的难题是

public class SessionLoggingMiddleware : IMiddleware, ITransientDependency
{
    private readonly IAbpSession _session;

    public SessionLoggingMiddleware(IAbpSession session)
    {
        _session = session;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        LogicalThreadContext.Properties["userid"] = _session.UserId;
        LogicalThreadContext.Properties["tenantid"] = _session.TenantId;
        await next(context);
    }
}

完整的代码可在 LeesStore PR #30 中找到。

请注意,使用其他附加程序(如 ApplicationInsightsAppender)与一些小的变化类似。

<appender name="AiAppender" 
 type="Microsoft.ApplicationInsights.Log4NetAppender.ApplicationInsightsAppender, 
 Microsoft.ApplicationInsights.Log4NetAppender">
  <threshold value="Info" />
  <layout type="log4net.Layout.PatternLayout">
    <conversionPattern 
     value="%-5level %property{userid} %property{tenantid} %message%newline"/>
  </layout>
</appender>

AdoNetAppender 稍微复杂一些

<appender name="AdoNetAppender" 
 type="MicroKnights.Logging.AdoNetAppender, MicroKnights.Log4NetAdoNetAppender">
  ..
  <commandText value="INSERT INTO LogEntries 
  ([Date],[Level],[Logger],[Message],[Exception],[UserId],[TenantId]) 
  VALUES (@log_date, @log_level, @logger, @message, @exception, @userid, @tenantid)" />
    ...
    <parameter>
      <parameterName value="@userid" />
      <dbType value="Int32" />
      <layout type="log4net.Layout.RawPropertyLayout">
        <key value="auserid" />
      </layout>
    </parameter>
</appender>

ABP Framework + Serilog

我喜欢 log4net。 我已经使用它很长时间了。 但 serilog 更现代,感觉更优雅。 使用其等效于“控制台附加程序”的自定义字段就像将它们放在 Program.cs 中设置的输出模板中的大括号中一样简单

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .WriteTo.Async(c => c.File("Logs/logs.txt", outputTemplate:
        "{Level:u4} [{UserId}] [{TenantId}] [{Timestamp:HH:mm:ss}] 
         {Message:lj}{NewLine}{Exception}"))
    .WriteTo.Async(c => c.Console())
    .CreateLogger();

设置这些自定义字段是通过 LogContext.PushProperty() 完成的。 在哪里放置该代码有点棘手。 我仍然是自定义中间件组件的粉丝,但插入 ABP Framework 中的中间件组件不会发生在 Startup.cs 中。 这是因为 ABP Framework 分散了该逻辑,允许每个依赖的模块注册自定义中间件。

但是,不需要自定义模块。 就像以前一样添加中间件组件,但在 [MyProject]ApiHostModuleOnApplicationInitialization() 方法中。

public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
    ...
    app.UseAuthentication();
    app.UseSessionLogging();
}

然后中间件组件本身与上一个组件非常相似

public class SessionLoggingMiddleware : IMiddleware, ITransientDependency
{
    private readonly ICurrentUser _currentUser;
    private readonly ICurrentTenant _currentTenant;

    public SessionLoggingMiddleware(ICurrentUser currentUser, ICurrentTenant currentTenant)
    {
        _currentUser = currentUser;
        _currentTenant = currentTenant;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        using (Serilog.Context.LogContext.PushProperty("UserId", _currentUser.Id))
        using (Serilog.Context.LogContext.PushProperty("TenantId", _currentTenant.Id))
        {
            await next(context);
        }
    }
}

现在控制台日志包含用户和租户。

或者您可能将它们发送到 Application Insights。 或者如果您将它们记录到数据库中,则可以在内部页面上公开它们

完整的更改在 LeesStore PR 31 中。

结论

这总结了两种截然不同的日志记录方法。 希望您能在这里学到一些东西来避免一些生产中的痛苦,或者只是为了培养您的艺术敏感性。

© . All rights reserved.