diff --git a/Core/MAX.Framework/DependencyInjection/LoggerExtensions.cs b/Core/MAX.Framework/DependencyInjection/LoggerExtensions.cs index 500f7dc..b4be8e1 100644 --- a/Core/MAX.Framework/DependencyInjection/LoggerExtensions.cs +++ b/Core/MAX.Framework/DependencyInjection/LoggerExtensions.cs @@ -1,22 +1,75 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using NLog.Web; -using System; -using System.Collections.Generic; -using System.Text; +using Serilog; +using Serilog.Debugging; namespace MAX.Framework.DependencyInjection { + /// + /// Serilog 日志接入:读取 appsettings.Serilog.json(文件缺失时回退到主配置里的 Serilog 节点), + /// 再把 Serilog 接入 ASP.NET Core 日志管线,使 ILogger<T> 与 Log 静态入口共用同一套 Sink。 + /// 日志按等级分目录(logs/等级/日期.log)由 appsettings.Serilog.json 里的 File sink + rollingInterval 负责。 + /// public static class LoggerExtensions { - public static WebApplicationBuilder AddLogger(this WebApplicationBuilder builder) + /// Serilog 独立配置文件(构建/发布会复制到输出目录)。 + private const string ConfigurationFileName = "appsettings.Serilog.json"; + + public static IHostApplicationBuilder AddLogger(this IHostApplicationBuilder host) { - builder.Logging.ClearProviders(); - //builder.Logging.SetMinimumLevel(LogLevel.Information); - builder.Host.UseNLog(); - return builder; + IConfigurationManager configuration = host.Configuration; + + // Serilog 默认会静默吞掉配置错误(Sink 名写错、过滤器语法错误等), + // 开发环境把自诊断信息输出到控制台,避免再出现「配置了却没有日志」却查不到原因。 + if (host.Environment.IsDevelopment()) + { + SelfLog.Enable(Console.Error.WriteLine); + } + + string? configurationPath = ResolveConfigurationPath(host); + if (configurationPath is null) + { + // 不直接抛异常:允许把 Serilog 节点写进 appsettings.json,但必须显式提示,而不是静默降级成「无任何 Sink」。 + Console.Error.WriteLine($"[Serilog] 未找到 {ConfigurationFileName},将使用主配置中的 Serilog 节点。"); + } + else + { + configuration.AddJsonFile(configurationPath, optional: false, reloadOnChange: true); + } + + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .Enrich.FromLogContext() + .WriteTo.Console() + .CreateLogger(); + + // 接管 ASP.NET Core 日志管线:宿主日志与 ILogger 统一写入 Log.Logger; + // dispose: true 让宿主停止时自动 CloseAndFlush,保证缓冲中的日志落盘。 + host.Services.AddSerilog(Log.Logger, dispose: true); + + return host; + } + + /// + /// 定位 Serilog 配置文件:支持绝对路径,其余情况依次在内容根目录(开发时即项目目录)与程序输出目录中查找。 + /// + private static string? ResolveConfigurationPath(IHostApplicationBuilder host) + { + if (Path.IsPathRooted(ConfigurationFileName)) + { + return File.Exists(ConfigurationFileName) ? ConfigurationFileName : null; + } + + string[] candidates = + [ + Path.Combine(host.Environment.ContentRootPath, ConfigurationFileName), + Path.Combine(AppContext.BaseDirectory, ConfigurationFileName), + ]; + + return candidates.FirstOrDefault(File.Exists); } } } diff --git a/Core/MAX.Framework/DependencyInjection/ServiceCollectionExtensions.cs b/Core/MAX.Framework/DependencyInjection/ServiceCollectionExtensions.cs index e5262de..6fa5a36 100644 --- a/Core/MAX.Framework/DependencyInjection/ServiceCollectionExtensions.cs +++ b/Core/MAX.Framework/DependencyInjection/ServiceCollectionExtensions.cs @@ -33,6 +33,7 @@ namespace MAX.Framework.DependencyInjection /// public static IServiceCollection AddStore(this IHostApplicationBuilder host) { + host.AddLogger(); //host.Services.AddSingleton(NLogger.Instance);//注册log IEnumerable assemblies = GetApplicationAssemblies(); diff --git a/Core/MAX.Framework/Logger/NLogger.cs b/Core/MAX.Framework/Logger/NLogger.cs deleted file mode 100644 index 6b287d9..0000000 --- a/Core/MAX.Framework/Logger/NLogger.cs +++ /dev/null @@ -1,138 +0,0 @@ -//using MAX.Core.Interfaces; -//using MAX.Core.Logger; -//using NLog; -//using System; -//using LLogLevel = MAX.Core.Logger.LogLevel; -//using NLogLevel = NLog.LogLevel; - -//namespace MAX.Framework.Logger; - -//public class NLogger : SingleInstance, ILoggerFacade -//{ -// private readonly ILogger _log; - -// private NLogger() -// { -// _log = LogManager.GetLogger("GlobalLogger"); -// } - -// public bool IsDebugEnabled { get; private set; } = true; - -// public void Debug(Exception ex, string message) -// { -// Execute(NLogLevel.Debug, ex, message); -// } - -// public void Debug(string message) -// { -// Execute(NLogLevel.Debug, message); -// } -// public void Error(Exception ex) -// { -// Execute(NLogLevel.Error, ex); -// } -// public void Error(Exception ex, string message) -// { -// Execute(NLogLevel.Error, ex, message); -// } - -// public void Error(string message) -// { -// Execute(NLogLevel.Error, message); -// } - -// public void Fatal(Exception ex, string message) -// { -// Execute(NLogLevel.Fatal, ex, message); -// } - -// public void Fatal(string message) -// { -// Execute(NLogLevel.Fatal, message); -// } - -// public void Info(Exception ex, string message) -// { -// Execute(NLogLevel.Info, ex, message); -// } - -// public void Info(string message) -// { -// Execute(NLogLevel.Info, message); -// } - -// public void Warn(Exception ex, string message) -// { -// Execute(NLogLevel.Warn, ex, message); -// } - -// public void Warn(string message) -// { -// Execute(NLogLevel.Warn, message); -// } -// public void Log(string message, LLogLevel level) -// { -// NLogLevel lv = NLogLevel.Info; -// switch (level) -// { -// case LLogLevel.Trace: -// lv = NLogLevel.Trace; -// break; -// case LLogLevel.Debug: -// lv = NLogLevel.Debug; -// break; -// case LLogLevel.Info: -// lv = NLogLevel.Info; -// break; -// case LLogLevel.Warn: -// lv = NLogLevel.Warn; -// break; -// case LLogLevel.Error: -// lv = NLogLevel.Error; -// break; -// case LLogLevel.Fatal: -// lv = NLogLevel.Fatal; -// break; -// case LLogLevel.None: -// lv = NLogLevel.Off; -// break; -// } -// Execute(lv, message); -// } -// /// -// /// 是否启用 -// /// -// private bool Enabled(NLogLevel level) -// { -// if (level > NLogLevel.Debug) -// { -// return true; -// } - -// return IsDebugEnabled; -// } - -// /// -// /// 执行 -// /// -// private void Execute(NLogLevel level, Exception ex, string message = "") -// { -// if (Enabled(level)) -// { -// _log.Log(level, ex, message, null); -// } -// } - - -// /// -// /// 执行 -// /// -// private void Execute(NLogLevel level, string message) -// { -// if (Enabled(level)) -// { -// _log.Log(level, message); -// } -// } - -//} diff --git a/Core/MAX.Framework/MAX.Framework.csproj b/Core/MAX.Framework/MAX.Framework.csproj index ccff2f7..504d8c4 100644 --- a/Core/MAX.Framework/MAX.Framework.csproj +++ b/Core/MAX.Framework/MAX.Framework.csproj @@ -16,9 +16,10 @@ - + + @@ -31,4 +32,10 @@ + + + PreserveNewest + + + diff --git a/Core/MAX.Framework/appsettings.Serilog.json b/Core/MAX.Framework/appsettings.Serilog.json new file mode 100644 index 0000000..996c119 --- /dev/null +++ b/Core/MAX.Framework/appsettings.Serilog.json @@ -0,0 +1,141 @@ +{ + "Serilog": { + "Using": [ + "Serilog.Sinks.Console", + "Serilog.Sinks.File", + "Serilog.Filters.Expressions" + ], + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Information", + "Microsoft.AspNetCore": "Warning", + "System": "Warning", + "Hangfire": "Information" + } + }, + "Enrich": [ + "FromLogContext" + ], + "Properties": { + "Application": "MyAspNetCore" + }, + "WriteTo": [ + { + "Name": "Logger", + "Args": { + "configureLogger": { + "Filter": [ + { "Name": "ByIncludingOnly", "Args": { "expression": "@Level = 'Debug'" } } + ], + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "Logs/Debug/.log", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}", + "retainedFileCountLimit": 365, + "fileSizeLimitBytes": 104857600, + "rollOnFileSizeLimit": true + } + } + ] + } + } + }, + { + "Name": "Logger", + "Args": { + "configureLogger": { + "Filter": [ + { "Name": "ByIncludingOnly", "Args": { "expression": "@Level = 'Information'" } } + ], + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "Logs/Info/.log", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}", + "retainedFileCountLimit": 365, + "fileSizeLimitBytes": 104857600, + "rollOnFileSizeLimit": true + } + } + ] + } + } + }, + { + "Name": "Logger", + "Args": { + "configureLogger": { + "Filter": [ + { "Name": "ByIncludingOnly", "Args": { "expression": "@Level = 'Warning'" } } + ], + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "Logs/Warning/.log", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}", + "retainedFileCountLimit": 365, + "fileSizeLimitBytes": 104857600, + "rollOnFileSizeLimit": true + } + } + ] + } + } + }, + { + "Name": "Logger", + "Args": { + "configureLogger": { + "Filter": [ + { "Name": "ByIncludingOnly", "Args": { "expression": "@Level = 'Error'" } } + ], + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "Logs/Error/.log", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}", + "retainedFileCountLimit": 365, + "fileSizeLimitBytes": 104857600, + "rollOnFileSizeLimit": true + } + } + ] + } + } + }, + { + "Name": "Logger", + "Args": { + "configureLogger": { + "Filter": [ + { "Name": "ByIncludingOnly", "Args": { "expression": "@Level = 'Fatal'" } } + ], + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "Logs/Fatal/.log", + "rollingInterval": "Day", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}", + "retainedFileCountLimit": 365, + "fileSizeLimitBytes": 104857600, + "rollOnFileSizeLimit": true + } + } + ] + } + } + } + ] + } +} diff --git a/Modules/CloudProduct/Store.CloudProduct.csproj b/Modules/CloudProduct/Store.CloudProduct.csproj index c8632f9..453bd03 100644 --- a/Modules/CloudProduct/Store.CloudProduct.csproj +++ b/Modules/CloudProduct/Store.CloudProduct.csproj @@ -10,12 +10,18 @@ - - + 由 builder.AddStoreHub() 自动并入宿主配置树(在 appsettings*.json 之后、环境变量之前)。 --> + + + + PreserveNewest + + diff --git a/Modules/CloudProduct/appsettings.json b/Modules/CloudProduct/appsettings.CloudProduct.json similarity index 100% rename from Modules/CloudProduct/appsettings.json rename to Modules/CloudProduct/appsettings.CloudProduct.json diff --git a/Store.API/Controllers/AuthController.cs b/Store.API/Controllers/AuthController.cs index d51369a..973d759 100644 --- a/Store.API/Controllers/AuthController.cs +++ b/Store.API/Controllers/AuthController.cs @@ -16,13 +16,15 @@ namespace Store.Api.Controllers private readonly ITokenService _tokenService; private readonly ICurrentUser _currentUser; private readonly IOnlineUserStore _onlineUserStore; + private readonly ILogger _logger; - public AuthController( ITokenService tokenService, ICurrentUser currentUser, IOnlineUserStore onlineUserStore) + public AuthController(ITokenService tokenService, ICurrentUser currentUser, IOnlineUserStore onlineUserStore, ILogger logger) { //_userService = userService; _tokenService = tokenService; _currentUser = currentUser; _onlineUserStore = onlineUserStore; + _logger = logger; } /// @@ -32,6 +34,8 @@ namespace Store.Api.Controllers [AllowAnonymous] public async Task Login([FromBody] LoginRequest request) { + _logger.LogError("login errr"); + _logger.LogInformation("login"); if (request is null || string.IsNullOrWhiteSpace(request.UserName) || string.IsNullOrWhiteSpace(request.Password)) { return ApiResult.Fail("用户名或密码不能为空", 400); diff --git a/Store.API/Program.cs b/Store.API/Program.cs index ee73e59..662eaea 100644 --- a/Store.API/Program.cs +++ b/Store.API/Program.cs @@ -1,5 +1,4 @@ using Asp.Versioning; -using MAX.Core.Logger; using MAX.Framework.DependencyInjection; using Scalar.AspNetCore; @@ -10,7 +9,7 @@ public partial class Program var builder = WebApplication.CreateBuilder(args); // IOC 注册:自动注册 ISingletonDependency / IScopedDependency / ITransientDependency,并执行所有 IStartup / IModule.AddModule - // 模块服务注册阶段(等价于 builder.Services.AddStoreHub(builder.Configuration, builder.Environment),额外把配置/环境交给模块) + // 模块服务注册阶段(等价于 builder.Services.AddStoreHub(builder.Configuration, builder.Environment),额外把配置/环境交给模块) builder.AddStore(); builder.Services.AddFreeSqlDB(); // JWT 验证:注册 JwtBearer 认证 + 令牌服务 + OpenAPI Bearer 安全方案(配置节 Jwt) @@ -37,7 +36,7 @@ public partial class Program WebApplication app = builder.Build(); - + app.UseStore(); // 初始化 KJD 迁移的静态提供者(MysqlUtils 连接提供 / 业务配置读取) //StoreHub.KJDWebService.Commons.AdoProvider.SetServiceProvider(app.Services); diff --git a/Store.API/Store.Api.csproj b/Store.API/Store.Api.csproj index ca96a0d..97aa213 100644 --- a/Store.API/Store.Api.csproj +++ b/Store.API/Store.Api.csproj @@ -7,12 +7,7 @@ - - - - - - + diff --git a/Store.API/dotnet-tools.json b/Store.API/dotnet-tools.json deleted file mode 100644 index 39007e0..0000000 --- a/Store.API/dotnet-tools.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "dotnet-ef": { - "version": "10.0.13", - "commands": [ - "dotnet-ef" - ], - "rollForward": false - } - } -} \ No newline at end of file