DDD Nlog、工作单元线程内唯一
从零开始用 .NET Core 写一个领域模型的框架
每篇文章都会打一个对应的 tag
Github 仓库地址
这一版代码中
引入 Nlog
之前版本是通过 ThreadLocal 实现工作单元线程内唯一,但是和 Task 异步方法配套使用的时候经常出现问题,非常的不稳定。这版使用 IHttpContextAccessor 实现工作单元线程内唯一
引入Nlog
------Program.cs using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NLog.Web; namespace Core2022.API { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>() .ConfigureLogging(logging => { logging.ClearProviders(); #if DEBUG logging.SetMinimumLevel(LogLevel.Debug); #else logging.SetMinimumLevel(LogLevel.Trace); #endif }).UseNLog(); }) //注册Autofac .UseServiceProviderFactory(new AutofacServiceProviderFactory()); } }
------------LogHelper.cs using NLog; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Core2022.Framework.Nlog { public static class LogHelper { private static readonly Logger logger; static LogHelper() { logger = LogManager.GetCurrentClassLogger(); } public static void Error(string title, Exception exception, [CallerMemberName] string methodName = "") { logger.Error(exception, title.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } public static void Error(string title, string msg, [CallerMemberName] string methodName = "") { logger.Error(title + msg.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } /// <summary> /// 调用外部接口 出错 统一用Warn /// </summary> /// <param name="title"></param> /// <param name="exception"></param> /// <param name="methodName"></param> public static void Warn(string title, Exception exception, [CallerMemberName] string methodName = "") { logger.Warn(exception, title.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } /// <summary> /// 调用外部接口 出错 统一用Warn /// </summary> /// <param name="title"></param> /// <param name="msg"></param> /// <param name="methodName"></param> public static void Warn(string title, string msg, [CallerMemberName] string methodName = "") { logger.Warn(title + msg.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } public static void Warn(string title, string msg, Exception exception, [CallerMemberName] string methodName = "") { logger.Warn(exception, title + msg.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } public static void Warn(string title, string msg, Dictionary<string, string> addInfo, [CallerMemberName] string methodName = "") { if (addInfo != null) { addInfo.Add("method", methodName); logger.Warn(title + msg.AppendDic(addInfo)); } else { logger.Warn(title + msg.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } } public static void Warn(string title, Exception ex, Dictionary<string, string> addInfo, [CallerMemberName] string methodName = "") { if (addInfo != null) { addInfo.Add("method", methodName); logger.Warn(ex, title.AppendDic(addInfo)); } else { logger.Warn(ex, title.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } } /// <summary> /// 内部接口出错 或者 打Debug日志 用Info /// </summary> /// <param name="title"></param> /// <param name="message"></param> /// <param name="methodName"></param> /// <param name="addInfo"></param> public static void Info(string title, string message, Dictionary<string, string> addInfo = null, [CallerMemberName] string methodName = "") { if (addInfo != null) { addInfo.Add("method", methodName); logger.Info(title + message.AppendDic(addInfo)); } else { logger.Info(title + message.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } } public static void Debug(string title, string message = "", Dictionary<string, string> addInfo = null, [CallerMemberName] string methodName = "") { if (addInfo != null) { addInfo.Add("method", methodName); logger.Debug(title + message.AppendDic(addInfo)); } else { logger.Debug(title + message.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } } /// <summary> /// 内部接口出错 或者 打Debug日志 用Info /// </summary> /// <param name="title"></param> /// <param name="exception"></param> /// <param name="methodName"></param> public static void Info(string title, Exception exception, [CallerMemberName] string methodName = "") { logger.Info(exception, title.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } public static void Debug(string title, Exception exception, [CallerMemberName] string methodName = "") { logger.Debug(exception, title.AppendDic(new Dictionary<string, string> { {"method",methodName} })); } } }
-------------nlog.config <?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <!-- enable asp.net core layout renderers --> <extensions> <add assembly="NLog.Web.AspNetCore"/> </extensions> <targets> <target name="asyncFile" xsi:type="AsyncWrapper"> <target name="log_file" xsi:type="File" fileName="${basedir}/Logs/${shortdate}/${logger}-${level}-${shortdate}.txt" layout="${longdate} | ${message} ${onexception:${exception:format=message} ${newline} ${stacktrace} ${newline}" archiveFileName="${basedir}/archives/${logger}-${level}-${shortdate}-{#####}.txt" archiveAboveSize="102400" archiveNumbering="Sequence" concurrentWrites="true" keepFileOpen="false" /> <target xsi:type="File" name="ownFile-web" fileName="${basedir}/Logs/nlog-all-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" /> </target> </targets> <rules> <logger name="Microsoft.*" minlevel="Info" final="true" /> <logger name="*" minlevel="Info" writeTo="asyncFile" /> </rules> </nlog>
项目内统一调用 LogHelper 中的方法
通过配置文件控制日志输出的级别
工作单元线程内唯一
由于项目中使用的都是 异步方法,导致从 ThreadLocal 获取工作单元的时候经常出现问题
---------------------HttpContext.cs using Autofac; using Microsoft.AspNetCore.Http; namespace Core2022.Framework.Web { public static class HttpContext { /// <summary> /// Startup.Configure 中对该属性初始化 /// </summary> public static ILifetimeScope ServiceProvider; public static Microsoft.AspNetCore.Http.HttpContext Current { get { IHttpContextAccessor factory = ServiceProvider.Resolve(typeof(IHttpContextAccessor)) as IHttpContextAccessor; Microsoft.AspNetCore.Http.HttpContext context = factory?.HttpContext; return context; } } } }
--------------------------------AppUnitOfWorkFactory.cs using Autofac; using Core2022.Framework.Web; namespace Core2022.Framework.UnitOfWork { /// <summary> /// 创建工作单元 /// 保证线程内唯一 /// </summary> public class AppUnitOfWorkFactory { public static IAppUnitOfWork GetAppUnitOfWorkRepository() { IAppUnitOfWork appUnitOfWork = HttpContext.Current.Items["AppUnitOfWork"] as IAppUnitOfWork; if (appUnitOfWork == null) { appUnitOfWork = CreateUnitOfWork(); HttpContext.Current.Items["AppUnitOfWork"] = appUnitOfWork; } return appUnitOfWork; } private static IAppUnitOfWork CreateUnitOfWork() { return Global.AutofacContainer.Resolve<IAppUnitOfWork>(); } #region 只读 public static IReadUnitOfWork GetReadUnitOfWorkRepository() { IReadUnitOfWork readUnitOfWork = HttpContext.Current.Items["ReadUnitOfWork"] as IReadUnitOfWork; if (readUnitOfWork == null) { readUnitOfWork = CreateReadUnitOfWork(); HttpContext.Current.Items["ReadUnitOfWork"] = readUnitOfWork; } return readUnitOfWork; } private static IReadUnitOfWork CreateReadUnitOfWork() { return Global.AutofacContainer.Resolve<IReadUnitOfWork>(); } #endregion } }
--------------------------------Startup.Configure 方法 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { ... ... HttpContext.ServiceProvider = app.ApplicationServices.GetAutofacRoot(); }
最后进行性能测试
从性能测试的结果和数据库中的数据综合来看,还行
10 个虚拟用户,压测30秒
每秒可以执行最少400次测试脚本
每次执行脚本最少要调用一个接口,最多要调用五个接口
每次测试完查询数据库最少能新增1W条数据