将用户名添加到 Serilog
Add Username into Serilog
我在 program.cs
中有这个 Serilog 配置
public class Program
{
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.Build();
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.WriteTo.MSSqlServer(Configuration.GetConnectionString("DefaultConnection"), "dbo.Log")
.Enrich.WithThreadId()
.Enrich.WithProperty("Version", "1.0.0")
.CreateLogger();
try
{
BuildWebHost(args).Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog()
.Build();
}
现在我想将 HttpContext.Current.User.Identity.Name
添加到所有日志消息中。
我尝试根据文档 https://github.com/serilog/serilog/wiki/Configuration-Basics#enrichers
创建新的 Enrich class
class UsernameEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, HttpContext httpContext)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
"Username", httpContext.User.Identity.Name));
}
}
但是与不知道HttpContext.
的ILogEventEnricher有冲突
我也尝试安装包含用户名 Enricher 的 Nuget 包 Serilog.Web.Classic,但是目标框架 .Net Framework 和 .Net Core 之间存在冲突,因此我无法使用这个插件。
有什么想法吗?
您可以创建一个中间件来将所需的 属性 放入 LogContext。
public class LogUserNameMiddleware
{
private readonly RequestDelegate next;
public LogUserNameMiddleware(RequestDelegate next)
{
this.next = next;
}
public Task Invoke(HttpContext context)
{
LogContext.PushProperty("UserName", context.User.Identity.Name);
return next(context);
}
}
您还需要将以下内容添加到记录器配置中:
.Enrich.FromLogContext()
在Startup中添加中间件LogUserNameMiddleware
,还要注意中间件要添加在UserAuthentication
之后,以便context.User.Identity
初始化
例如
app.UseAuthentication();
app.UseMiddleware<LogUserNameMiddleware>();
使用中间件的替代方法是使用操作过滤器。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Serilog.Context;
namespace Acme.Widgets.Infrastructure
{
public class LogEnrichmentFilter : IActionFilter
{
private readonly IHttpContextAccessor httpContextAccessor;
public LogEnrichmentFilter(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void OnActionExecuting(ActionExecutingContext context)
{
var httpUser = this.httpContextAccessor.HttpContext.User;
if (httpUser.Identity.IsAuthenticated)
{
var appUser = new AppIdentity(httpUser);
LogContext.PushProperty("Username", appUser.Username);
}
else
{
LogContext.PushProperty("Username", "-");
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do nothing
}
}
}
在您的 Startup.ConfigureServices
中,您需要:
- 确保
IHttpContextAccessor
添加到 IoC 容器
- 将
LogEnrichmentFilter
添加到 IoC 容器,范围限定为请求
- 将
LogEnrichmentFilter
注册为全局操作过滤器
Startup.cs
:
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<LogEnrichmentFilter>();
services.AddMvc(o =>
{
o.Filters.Add<LogEnrichmentFilter>();
});
然后,对于 MVC action invocation pipeline. I imagine the username would be attached to a few more log entries if you used a resource filter 中 运行 的代码,您应该在日志上下文中使用当前用户名,而不是操作过滤器,因为它们 运行 在管道中稍微早一点(我才刚刚知道这些!)
@Alex Riabov 建议的方法存在许多问题。
- 需要
Dispose
推送 属性
- 中间件中的
Invoke
方法是异步的,所以你不能只return next()
,你需要await next()
- 请求信息由
UseSerilogRequestLogging()
中间件记录。如果 属性 在到达之前弹出,则 属性 变为空。
要修复它们,我可以建议进行以下修改。
在中间件中:
public async Task Invoke(HttpContext context)
{
using (LogContext.PushProperty("UserName", context.User.Identity.Name ?? "anonymous"))
{
await next(context);
}
}
在Startup.cs
中:
appl.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseMiddleware<SerilogUserNameMiddleware>()
.UseSerilogRequestLogging()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapHealthChecks("/health");
});
如果您使用 Serilog.AspNetCore,添加 authentication/user 属性非常容易。
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = PushSeriLogProperties;
});
public void PushSeriLogProperties(IDiagnosticContext diagnosticContext, HttpContext httpContext)
{
diagnosticContext.Set("SomePropertyName", httpContext.User...);
}
只需两步即可实现
1- 创建一个可以访问服务的 Enricher。
using Microsoft.AspNetCore.Http;
using Serilog.Core;
using Serilog.Events;
using System.Security.Claims;
namespace CoolProject.Logging.Enricher;
public class UserEnricher : ILogEventEnricher
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserEnricher() : this(new HttpContextAccessor())
{
}
//Dependency injection can be used to retrieve any service required to get a user or any data.
//Here, I easily get data from HTTPContext
public UserEnricher(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
"UserId", _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous"));
}
}
2-使用 With 来包含您的 UserEnricher。
loggerConfiguration.Enrich.FromLogContext()
.MinimumLevel.Is(level)
.Enrich.With<UserEnricher>()
添加用户增强器只需要两步,但我也会添加我的驱动程序代码。 别忘了注入 IHttpContextAccessor!
public static IHostBuilder UseLogging(this IHostBuilder webHostBuilder, string applicationName = null)
=> webHostBuilder.UseSerilog((context ,loggerConfiguration) =>
{
var logOptions = context.Configuration.GetSection("logging");
var serilogOptions = logOptions.GetSection("serilog").Get<SerilogOptions>();
if (!Enum.TryParse<LogEventLevel>(serilogOptions.Level, true, out var level))
{
level = LogEventLevel.Error;
}
loggerConfiguration.Enrich.FromLogContext()
.MinimumLevel.Is(level)
.Enrich.With<UserEnricher>()
.Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
.Enrich.WithProperty("ApplicationName", applicationName);
loggerConfiguration.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} [{Level}] {Environment} {ApplicationName} {UserId} {Message:lj}{NewLine}{Exception}");
});
我在 program.cs
中有这个 Serilog 配置public class Program
{
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.Build();
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.WriteTo.MSSqlServer(Configuration.GetConnectionString("DefaultConnection"), "dbo.Log")
.Enrich.WithThreadId()
.Enrich.WithProperty("Version", "1.0.0")
.CreateLogger();
try
{
BuildWebHost(args).Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog()
.Build();
}
现在我想将 HttpContext.Current.User.Identity.Name
添加到所有日志消息中。
我尝试根据文档 https://github.com/serilog/serilog/wiki/Configuration-Basics#enrichers
创建新的 Enrich classclass UsernameEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, HttpContext httpContext)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
"Username", httpContext.User.Identity.Name));
}
}
但是与不知道HttpContext.
的ILogEventEnricher有冲突我也尝试安装包含用户名 Enricher 的 Nuget 包 Serilog.Web.Classic,但是目标框架 .Net Framework 和 .Net Core 之间存在冲突,因此我无法使用这个插件。
有什么想法吗?
您可以创建一个中间件来将所需的 属性 放入 LogContext。
public class LogUserNameMiddleware
{
private readonly RequestDelegate next;
public LogUserNameMiddleware(RequestDelegate next)
{
this.next = next;
}
public Task Invoke(HttpContext context)
{
LogContext.PushProperty("UserName", context.User.Identity.Name);
return next(context);
}
}
您还需要将以下内容添加到记录器配置中:
.Enrich.FromLogContext()
在Startup中添加中间件LogUserNameMiddleware
,还要注意中间件要添加在UserAuthentication
之后,以便context.User.Identity
初始化
例如
app.UseAuthentication();
app.UseMiddleware<LogUserNameMiddleware>();
使用中间件的替代方法是使用操作过滤器。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Serilog.Context;
namespace Acme.Widgets.Infrastructure
{
public class LogEnrichmentFilter : IActionFilter
{
private readonly IHttpContextAccessor httpContextAccessor;
public LogEnrichmentFilter(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void OnActionExecuting(ActionExecutingContext context)
{
var httpUser = this.httpContextAccessor.HttpContext.User;
if (httpUser.Identity.IsAuthenticated)
{
var appUser = new AppIdentity(httpUser);
LogContext.PushProperty("Username", appUser.Username);
}
else
{
LogContext.PushProperty("Username", "-");
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do nothing
}
}
}
在您的 Startup.ConfigureServices
中,您需要:
- 确保
IHttpContextAccessor
添加到 IoC 容器 - 将
LogEnrichmentFilter
添加到 IoC 容器,范围限定为请求 - 将
LogEnrichmentFilter
注册为全局操作过滤器
Startup.cs
:
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<LogEnrichmentFilter>();
services.AddMvc(o =>
{
o.Filters.Add<LogEnrichmentFilter>();
});
然后,对于 MVC action invocation pipeline. I imagine the username would be attached to a few more log entries if you used a resource filter 中 运行 的代码,您应该在日志上下文中使用当前用户名,而不是操作过滤器,因为它们 运行 在管道中稍微早一点(我才刚刚知道这些!)
@Alex Riabov 建议的方法存在许多问题。
- 需要
Dispose
推送 属性 - 中间件中的
Invoke
方法是异步的,所以你不能只return next()
,你需要await next()
- 请求信息由
UseSerilogRequestLogging()
中间件记录。如果 属性 在到达之前弹出,则 属性 变为空。
要修复它们,我可以建议进行以下修改。
在中间件中:
public async Task Invoke(HttpContext context)
{
using (LogContext.PushProperty("UserName", context.User.Identity.Name ?? "anonymous"))
{
await next(context);
}
}
在Startup.cs
中:
appl.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseMiddleware<SerilogUserNameMiddleware>()
.UseSerilogRequestLogging()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapHealthChecks("/health");
});
如果您使用 Serilog.AspNetCore,添加 authentication/user 属性非常容易。
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = PushSeriLogProperties;
});
public void PushSeriLogProperties(IDiagnosticContext diagnosticContext, HttpContext httpContext)
{
diagnosticContext.Set("SomePropertyName", httpContext.User...);
}
只需两步即可实现
1- 创建一个可以访问服务的 Enricher。
using Microsoft.AspNetCore.Http;
using Serilog.Core;
using Serilog.Events;
using System.Security.Claims;
namespace CoolProject.Logging.Enricher;
public class UserEnricher : ILogEventEnricher
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserEnricher() : this(new HttpContextAccessor())
{
}
//Dependency injection can be used to retrieve any service required to get a user or any data.
//Here, I easily get data from HTTPContext
public UserEnricher(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
"UserId", _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous"));
}
}
2-使用 With 来包含您的 UserEnricher。
loggerConfiguration.Enrich.FromLogContext()
.MinimumLevel.Is(level)
.Enrich.With<UserEnricher>()
添加用户增强器只需要两步,但我也会添加我的驱动程序代码。 别忘了注入 IHttpContextAccessor!
public static IHostBuilder UseLogging(this IHostBuilder webHostBuilder, string applicationName = null)
=> webHostBuilder.UseSerilog((context ,loggerConfiguration) =>
{
var logOptions = context.Configuration.GetSection("logging");
var serilogOptions = logOptions.GetSection("serilog").Get<SerilogOptions>();
if (!Enum.TryParse<LogEventLevel>(serilogOptions.Level, true, out var level))
{
level = LogEventLevel.Error;
}
loggerConfiguration.Enrich.FromLogContext()
.MinimumLevel.Is(level)
.Enrich.With<UserEnricher>()
.Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName)
.Enrich.WithProperty("ApplicationName", applicationName);
loggerConfiguration.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} [{Level}] {Environment} {ApplicationName} {UserId} {Message:lj}{NewLine}{Exception}");
});