using System; using System.Text.Json; using KH.WMS.Core.DependencyInjection.ServiceLifetimes; using KH.WMS.Core.Logging.LogEnums; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SerilogLogContext = Serilog.Context.LogContext; namespace KH.WMS.Core.Logging.Serilog; /// /// 日志服务实现(自动识别模块) /// [RegisteredService(Lifetime = ServiceLifetime.Scoped)] public class LoggerService : ILoggerService { private readonly ILogger _logger; private readonly IHttpContextAccessor? _httpContextAccessor; public LoggerService(ILogger logger, IHttpContextAccessor? httpContextAccessor = null) { _logger = logger; _httpContextAccessor = httpContextAccessor; } public void LogVerbose(string message, params object?[] args) { var module = LogModuleDetector.DetectModule(); WriteLog(LogLevel.Trace, message, module, null, args); } public void LogDebug(string message, params object?[] args) { var module = LogModuleDetector.DetectModule(); WriteLog(LogLevel.Debug, message, module, null, args); } public void LogInfo(string message, params object?[] args) { var module = LogModuleDetector.DetectModule(); WriteLog(LogLevel.Information, message, module, null, args); } public void LogWarning(string message, params object?[] args) { var module = LogModuleDetector.DetectModule(); WriteLog(LogLevel.Warning, message, module, null, args); } public void LogError(string message, params object?[] args) { var module = LogModuleDetector.DetectModule(); WriteLog(LogLevel.Error, message, module, null, args); } public void LogError(Exception exception, string message, params object?[] args) { var module = LogModuleDetector.DetectModule(); var context = CreateLogContext(LogLevel.Error, module); context.Type = LogType.Exception; using (PushFileName(null)) { if (args != null && args.Length > 0) { var messageTemplate = $"[{GetModuleCode(module)}] {message}"; _logger.LogError(exception, messageTemplate, args); } else { _logger.LogError(exception, "[{ModuleCode}] {Message}", GetModuleCode(module), message); } } } public void LogFatal(string message, params object?[] args) { var module = LogModuleDetector.DetectModule(); WriteLog(LogLevel.Critical, message, module, null, args); } public void LogFatal(Exception exception, string message, params object?[] args) { var module = LogModuleDetector.DetectModule(); var context = CreateLogContext(LogLevel.Critical, module); context.Type = LogType.Exception; using (PushFileName(null)) { if (args != null && args.Length > 0) { var messageTemplate = $"[{GetModuleCode(module)}] {message}"; _logger.LogCritical(exception, messageTemplate, args); } else { _logger.LogCritical(exception, "[{ModuleCode}] {Message}", GetModuleCode(module), message); } } } public void LogOperation(string operation, string? userName = null, long? userId = null, object? data = null) { var module = LogModuleDetector.DetectModule(); var context = CreateLogContext(LogLevel.Information, module); context.Type = LogType.Operation; context.Operation = operation; context.UserName = userName ?? context.UserName; context.UserId = userId ?? context.UserId; var dataJson = data != null ? JsonSerializer.Serialize(data) : null; using (PushFileName(null)) { _logger.LogInformation("[{ModuleCode}] [OPERATION] {Operation} by {UserName} (UserId:{UserId}) {Data}", GetModuleCode(module), operation, context.UserName, context.UserId, dataJson ?? ""); } } public void LogBusiness(string businessType, string message, object? data = null) { var module = LogModuleDetector.DetectModule(); var context = CreateLogContext(LogLevel.Information, module); context.Type = LogType.Business; context.Operation = businessType; var dataJson = data != null ? JsonSerializer.Serialize(data) : null; using (PushFileName(null)) { _logger.LogInformation("[{ModuleCode}] [BUSINESS:{Type}] {Message} {Data}", GetModuleCode(module), businessType, message, dataJson ?? ""); } } public void LogPerformance(string operation, long elapsedMs, object? data = null) { var module = LogModuleDetector.DetectModule(); var context = CreateLogContext(LogLevel.Information, module); context.Type = LogType.Performance; context.Operation = operation; context.Data["ElapsedMs"] = elapsedMs; context.Data["ElapsedSeconds"] = elapsedMs / 1000.0; if (elapsedMs > 3000) { context.Level = LogLevelType.Warning; } var dataJson = data != null ? JsonSerializer.Serialize(data) : null; var logLevel = elapsedMs > 3000 ? LogLevel.Warning : LogLevel.Information; using (PushFileName(null)) { _logger.Log(logLevel, "[{ModuleCode}] [PERFORMANCE] {Operation} 耗时 {Elapsed}ms {Data}", GetModuleCode(module), operation, elapsedMs, dataJson ?? ""); } } public void Log(LogContext context, string message, params object?[] args) { var logLevel = ConvertLogLevel(context.Level); var messageFormat = $"[{GetModuleCode(context.Module)}] [LogType:{context.Type}] {message}"; var state = new Dictionary { ["UserId"] = context.UserId, ["UserName"] = context.UserName, ["TenantId"] = context.TenantId, ["RequestId"] = context.RequestId, ["Operation"] = context.Operation }; using (_logger.BeginScope(state)) { _logger.Log(logLevel, messageFormat, args); } } // ==================== 私有方法 ==================== private void WriteLog(LogLevel level, string message, LogModule module, string? fileName, object?[] args) { using (PushFileName(fileName)) { if (args != null && args.Length > 0) { var messageTemplate = $"[{GetModuleCode(module)}] {message}"; _logger.Log(level, messageTemplate, args); } else { _logger.Log(level, $"[{GetModuleCode(module)}] {message}"); } } } private IDisposable? PushFileName(string? fileName) { if (string.IsNullOrEmpty(fileName)) { return null; } return SerilogLogContext.PushProperty("FileName", fileName); } private LogContext CreateLogContext(LogLevel level, LogModule module) { var context = new LogContext { Level = ConvertToLogLevelType(level), Module = module }; if (_httpContextAccessor?.HttpContext != null) { var httpContext = _httpContextAccessor.HttpContext; var user = httpContext.User; var userIdClaim = user?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (long.TryParse(userIdClaim, out var userId)) { context.UserId = userId; } context.UserName = user?.FindFirst(System.Security.Claims.ClaimTypes.Name)?.Value; var tenantIdClaim = user?.FindFirst("TenantId")?.Value; if (long.TryParse(tenantIdClaim, out var tenantId)) { context.TenantId = tenantId; } context.RequestId = httpContext.TraceIdentifier; context.CorrelationId = httpContext.Request.Headers["X-Correlation-ID"].FirstOrDefault(); context.ClientIp = httpContext.Connection.RemoteIpAddress?.ToString(); } return context; } private LogLevel ConvertLogLevel(LogLevelType level) { return level switch { LogLevelType.Verbose => LogLevel.Trace, LogLevelType.Debug => LogLevel.Debug, LogLevelType.Information => LogLevel.Information, LogLevelType.Warning => LogLevel.Warning, LogLevelType.Error => LogLevel.Error, LogLevelType.Fatal => LogLevel.Critical, LogLevelType.None => LogLevel.None, _ => LogLevel.Information }; } private LogLevelType ConvertToLogLevelType(LogLevel level) { return level switch { Microsoft.Extensions.Logging.LogLevel.Trace => LogLevelType.Verbose, Microsoft.Extensions.Logging.LogLevel.Debug => LogLevelType.Debug, Microsoft.Extensions.Logging.LogLevel.Information => LogLevelType.Information, Microsoft.Extensions.Logging.LogLevel.Warning => LogLevelType.Warning, Microsoft.Extensions.Logging.LogLevel.Error => LogLevelType.Error, Microsoft.Extensions.Logging.LogLevel.Critical => LogLevelType.Fatal, Microsoft.Extensions.Logging.LogLevel.None => LogLevelType.None, _ => LogLevelType.Information }; } private string GetModuleCode(LogModule module) { return ((int)module).ToString() + " " + module; } private string GetModuleName(LogModule module) { return module.ToString(); } }