.NET 6新特性试用 | 自动生成高性能日志记录代码
前言
要想記錄日志,常用的方式是訪問ILogger實例提供的日志記錄方法:
private?readonly?ILogger<WeatherForecastController>?_logger;public?WeatherForecastController(ILogger<WeatherForecastController>?logger) {_logger?=?logger; }[HttpGet(Name?=?"GetWeatherForecast")] public?IEnumerable<WeatherForecast>?Get() {?var?result?=??Enumerable.Range(1,?5).Select(index?=>?new?WeatherForecast{TemperatureC?=?Random.Shared.Next(-20,?55),}).ToArray();_logger.LogInformation("LogInformation:?{0}",?JsonSerializer.Serialize(result));return?result; }其實,.NET下還有一個高性能日志記錄類LoggerMessage[1]。
與ILogger記錄器擴展方法(例如LogInformation和LogDebug)相比,LoggerMessage具有以下性能優勢:
記錄器擴展方法需要將值類型(例如 int)“裝箱”(轉換)到 object中。LoggerMessage模式使用帶強類型參數的靜態Action字段和擴展方法來避免裝箱。
記錄器擴展方法每次寫入日志消息時必須分析消息模板(命名的格式字符串)。如果已定義消息,那么LoggerMessage只需分析一次模板即可。
示例代碼如下:
private?static?readonly?Action<ILogger,?IEnumerable<WeatherForecast>,?Exception?>?_logWeatherForecast?=LoggerMessage.Define<IEnumerable<WeatherForecast>>(logLevel:?LogLevel.Information,eventId:?0,formatString:?"LoggerMessage:?{aa}");//使用 _logWeatherForecast(_logger,?result,?null);雖然使用LoggerMessage可以為我們提供更好的性能,但是,需要手工編寫大量的LoggerMessage.Define代碼;而且formatString消息模板中的參數占位符并沒有任何控制(例如{aa}),很可能導致傳遞錯誤參數。
而在.NET 6中,可以使用Source Generator幫助我們自動生成高性能日志記錄代碼。
Demo
你需要創建一個partial方法,然后在其頭部聲明LoggerMessageAttribute。
示例代碼如下:
[LoggerMessage(0,?LogLevel.Information,?"LoggerMessageAttribute:?{weatherForecasts}")] partial?void?LogWeatherForecast(IEnumerable<WeatherForecast>?weatherForecasts);//使用 LogWeatherForecast(result);查看自動生成的代碼,其實是Source Generator幫我們編寫了LoggerMessage.Define代碼:
partial?class?WeatherForecastController? {[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators",?"6.0.5.2210")]private?static?readonly?global::System.Action<global::Microsoft.Extensions.Logging.ILogger,?global::System.Collections.Generic.IEnumerable<global::WebApplication1.WeatherForecast>,?global::System.Exception?>?__LogWeatherForecastCallback?=global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.Collections.Generic.IEnumerable<global::WebApplication1.WeatherForecast>>(global::Microsoft.Extensions.Logging.LogLevel.Information,?new?global::Microsoft.Extensions.Logging.EventId(0,?nameof(LogWeatherForecast)),?"LoggerMessageAttribute:?{weatherForecasts}",?new?global::Microsoft.Extensions.Logging.LogDefineOptions()?{?SkipEnabledCheck?=?true?});?[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators",?"6.0.5.2210")]partial?void?LogWeatherForecast(global::System.Collections.Generic.IEnumerable<global::WebApplication1.WeatherForecast>?weatherForecasts){if?(_logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information)){__LogWeatherForecastCallback(_logger,?weatherForecasts,?null);}} }LogWeatherForecast方法直接使用了Controller中聲明的_logger對象,并不需要我們傳入;而且寫入日志前判斷了_logger.IsEnabled避免不必要的日志寫入操作,對性能有進一步提高。
更為重要的是,它不會允許傳入錯誤的參數:
結論
使用LoggerMessageAttribute可以提高日志記錄性能,但它也有其缺點:
使用partial方法聲明必須將類也定義成partial。
日志使用了參數對象的ToString()方法,對于復雜類型,不能在方法中傳入序列化對象LogWeatherForecast(JsonSerializer.Serialize(result)),因為會始終執行影響性能,可以通過定義成record class或自定義ToString()方法變通解決:
參考資料
[1]
LoggerMessage: https://docs.microsoft.com/zh-cn/dotnet/core/extensions/high-performance-logging
如果你覺得這篇文章對你有所啟發,請關注我的個人公眾號”My IO“
總結
以上是生活随笔為你收集整理的.NET 6新特性试用 | 自动生成高性能日志记录代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何使用cURL获得请求和响应时间?
- 下一篇: GraphQL 到底有什么魔力?