187 lines
7.9 KiB
C#
187 lines
7.9 KiB
C#
using Newtonsoft.Json.Linq;
|
|
using Serilog.Core;
|
|
using Serilog.Events;
|
|
using Serilog;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Newtonsoft.Json;
|
|
using System.IO;
|
|
using ServiceStack;
|
|
using System.Globalization;
|
|
|
|
namespace BZPT.Api.Middleware
|
|
{
|
|
public static class LogExt
|
|
{
|
|
public static void UseMySerilog(this WebApplicationBuilder builder, string connection, string serviceName)
|
|
{
|
|
builder.Services.AddHttpContextAccessor();
|
|
// 配置日志
|
|
//var log = builder.Logging.AddLog4Net();
|
|
//log.AddConsole();
|
|
//log.AddDebug();
|
|
//log.SetMinimumLevel(LogLevel.Debug);
|
|
// 配置 Serilog
|
|
var httpContextAccessor = builder.Services.BuildServiceProvider().GetRequiredService<IHttpContextAccessor>();
|
|
|
|
// 配置 Serilog
|
|
Log.Logger = new LoggerConfiguration()
|
|
.MinimumLevel.Debug()
|
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
|
.MinimumLevel.Override("Hangfire", LogEventLevel.Warning)
|
|
.MinimumLevel.Override("IdentityServer4", LogEventLevel.Debug)
|
|
.Enrich.With(new SensitiveDataFilterEnricher()) // 敏感词过滤
|
|
.Enrich.With(new RequestContextEnricher(httpContextAccessor))
|
|
.Enrich.With(new OperationObjectEnricher(httpContextAccessor))
|
|
//.Enrich.With(new ParamsEnricher(httpContextAccessor))
|
|
.Enrich.With(new ModuleTypeEnricher(httpContextAccessor))
|
|
.Enrich.FromLogContext()
|
|
.WriteTo.Console(
|
|
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3} {EventId:l}] " +
|
|
"{CategoryName}: {Message:lj}{NewLine}{Exception}",
|
|
formatProvider: CultureInfo.InvariantCulture
|
|
)
|
|
.WriteTo.Async(a => a.Sink(new LoggingWithDMSink(connection, serviceName,
|
|
httpContextAccessor,
|
|
batchSizeLimit: 200,
|
|
period: TimeSpan.FromSeconds(30)
|
|
))
|
|
)
|
|
// 信息日志批量写入文件(异步)
|
|
.WriteTo.Logger(lc => lc
|
|
.Filter.ByIncludingOnly(e => e.Level <= LogEventLevel.Information)
|
|
.WriteTo.Async(a => a.File(
|
|
path: "logs/info-.txt",
|
|
rollingInterval: RollingInterval.Day,
|
|
retainedFileCountLimit: 7,
|
|
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}",
|
|
buffered: true, // 启用缓冲
|
|
flushToDiskInterval: TimeSpan.FromSeconds(5) // 5秒刷盘
|
|
))
|
|
)
|
|
// 其他日志处理(可选)
|
|
.CreateLogger();
|
|
builder.Host.UseSerilog();
|
|
}
|
|
}
|
|
|
|
public class RequestContextEnricher : ILogEventEnricher
|
|
{
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
|
|
public RequestContextEnricher(IHttpContextAccessor httpContextAccessor)
|
|
{
|
|
_httpContextAccessor = httpContextAccessor;
|
|
}
|
|
|
|
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory factory)
|
|
{
|
|
var context = _httpContextAccessor.HttpContext;
|
|
if (context == null) return;
|
|
var forwardedIp = context.Request.Headers["X-Forwarded-For"].FirstOrDefault()?.Split(',')[0].Trim();
|
|
var clientIp = forwardedIp ?? context.Connection.RemoteIpAddress?.ToString();
|
|
// 基础信息
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty("RequestID", context.Items["X-Request-ID"]));
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty("UserID", context.User.FindFirstValue(ClaimTypes.NameIdentifier)));
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty("UserName", context.User.FindFirstValue(ClaimTypes.Name)));
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty("IP", clientIp));
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty("UserAgent", context.Request.Headers["User-Agent"].ToString()));
|
|
}
|
|
}
|
|
public class OperationObjectEnricher : ILogEventEnricher
|
|
{
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
|
|
public OperationObjectEnricher(IHttpContextAccessor httpContextAccessor)
|
|
{
|
|
_httpContextAccessor = httpContextAccessor;
|
|
}
|
|
|
|
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory factory)
|
|
{
|
|
var context = _httpContextAccessor.HttpContext;
|
|
if (context == null) return;
|
|
|
|
// 从路由参数或请求体中提取对象ID/类型
|
|
var objId = context.Request.RouteValues["id"]?.ToString()
|
|
?? GetFromBody(context, "id");
|
|
|
|
var objType = context.Request.RouteValues["controller"]?.ToString();
|
|
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty("ObjID", objId));
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty("ObjType", objType));
|
|
}
|
|
|
|
private string GetFromBody(HttpContext context, string key)
|
|
{
|
|
try
|
|
{
|
|
if (context.Request.HasJsonContentType())
|
|
{
|
|
context.Request.Body.Seek(0, SeekOrigin.Begin);
|
|
using var reader = new StreamReader(context.Request.Body);
|
|
var json = reader.ReadToEndAsync().Result;
|
|
var jObject = JObject.Parse(json);
|
|
return jObject[key]?.ToString();
|
|
}
|
|
}
|
|
catch { /* 安全忽略解析错误 */ }
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
public class ModuleTypeEnricher : ILogEventEnricher
|
|
{
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
|
|
public ModuleTypeEnricher(IHttpContextAccessor httpContextAccessor)
|
|
{
|
|
_httpContextAccessor = httpContextAccessor;
|
|
}
|
|
|
|
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory factory)
|
|
{
|
|
var context = _httpContextAccessor.HttpContext;
|
|
if (context == null) return;
|
|
|
|
// 示例路径: /api/v1/orders/create -> Module=Orders, Type=Create
|
|
var path = context.Request.Path.Value?.Split('/');
|
|
var module = path?.Length >= 2 ? path[path.Length-2] : "Unknown";
|
|
|
|
var operationType = path?.LastOrDefault()?.ToUpper();
|
|
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty("ModuleName", module));
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty("OperationType", operationType));
|
|
}
|
|
}
|
|
|
|
// SensitiveDataFilterEnricher.cs
|
|
public class SensitiveDataFilterEnricher : ILogEventEnricher
|
|
{
|
|
private readonly Regex _sensitivePattern;
|
|
private readonly string _replacement;
|
|
|
|
public SensitiveDataFilterEnricher(string pattern = @"\b(?:password|creditcard|cvv|token)\b",
|
|
string replacement = "***REDACTED***")
|
|
{
|
|
_sensitivePattern = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
|
_replacement = replacement;
|
|
}
|
|
|
|
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory factory)
|
|
{
|
|
// 过滤所有字符串类型属性
|
|
foreach (var prop in logEvent.Properties.ToList())
|
|
{
|
|
if (prop.Value is ScalarValue scalar && scalar.Value is string strVal)
|
|
{
|
|
var cleanVal = _sensitivePattern.Replace(strVal, _replacement);
|
|
logEvent.AddOrUpdateProperty(factory.CreateProperty(prop.Key, cleanVal));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|