如何避免异步方法中日志与异常消息模板重复的代码模式?
解决方案:统一日志与异常消息,避免重复模板
针对你遇到的问题——既要保留结构化日志的优势,又要让异常消息与日志一致,同时不重复编写模板字符串,这里提供两种实用方案,均支持异步方法:
方案一:通用扩展方法(灵活适配多场景)
通过扩展ILogger,实现一个能同时完成结构化日志记录和异常抛出的通用方法,模板只需编写一次:
using System; using System.Globalization; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; public static class LoggerExtensions { /// <summary> /// 记录Error级别的结构化日志,并抛出指定类型的异常 /// </summary> /// <typeparam name="TException">要抛出的异常类型(需包含带string参数的构造函数)</typeparam> /// <param name="logger">日志实例</param> /// <param name="messageTemplate">结构化日志模板</param> /// <param name="args">模板参数</param> public static void LogErrorAndThrow<TException>(this ILogger logger, string messageTemplate, params object[] args) where TException : Exception { // 先记录结构化日志,保留参数可查询的优势 logger.LogError(messageTemplate, args); // 格式化模板生成异常消息 var formattedMessage = FormatMessageTemplate(messageTemplate, args); throw (TException)Activator.CreateInstance(typeof(TException), formattedMessage)!; } // 解析结构化日志模板,替换占位符为实际参数值 private static string FormatMessageTemplate(string messageTemplate, object[] args) { var regex = new Regex(@"\{(\w+)\}"); int paramIndex = 0; return regex.Replace(messageTemplate, match => { if (paramIndex >= args.Length) return match.Value; return args[paramIndex++]?.ToString() ?? match.Value; }); } }
异步方法中使用示例:
public async Task<int> DoSomeWork(int numerator, int denominator, ILogger logger) { if (denominator == 0) { logger.LogErrorAndThrow<ApplicationException>( "The division : {Numerator}/{Denominator} is invalid as the denominator is equal to zero", numerator, denominator); } await Task.Delay(TimeSpan.FromSeconds(1)); return numerator / denominator; }
方案二:类型安全的专用方法(适合固定错误场景)
如果你的错误场景比较固定,可以为每个场景编写专用的扩展方法,避免装箱操作,同时保证类型安全:
using System; using System.Globalization; using Microsoft.Extensions.Logging; public static class DivisionErrorLoggerExtensions { private const string DenominatorZeroTemplate = "The division : {Numerator}/{Denominator} is invalid as the denominator is equal to zero"; public static void LogDenominatorZeroAndThrow(this ILogger logger, int numerator, int denominator) { // 记录结构化日志 logger.LogError(DenominatorZeroTemplate, numerator, denominator); // 生成异常消息(替换模板占位符为索引格式,再格式化) var message = string.Format( CultureInfo.InvariantCulture, DenominatorZeroTemplate.Replace("{Numerator}", "{0}").Replace("{Denominator}", "{1}"), numerator, denominator); throw new ApplicationException(message); } }
异步方法中使用示例:
public async Task<int> DoSomeWork(int numerator, int denominator, ILogger logger) { if (denominator == 0) { logger.LogDenominatorZeroAndThrow(numerator, denominator); } await Task.Delay(TimeSpan.FromSeconds(1)); return numerator / denominator; }
方案说明
- 两种方案都完全支持异步方法:日志记录和异常抛出是同步操作,不会干扰异步流程的执行。
- 保留了结构化日志的核心优势:日志系统仍能捕获独立的参数(如
Numerator、Denominator),方便后续查询和分析。 - 彻底避免了重复编写模板字符串:模板仅在一处定义,日志和异常消息均基于同一模板生成。
内容的提问来源于stack exchange,提问作者Angus Millar
相关产品推荐
相关产品推荐

