You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免异步方法中日志与异常消息模板重复的代码模式?

解决方案:统一日志与异常消息,避免重复模板

针对你遇到的问题——既要保留结构化日志的优势,又要让异常消息与日志一致,同时不重复编写模板字符串,这里提供两种实用方案,均支持异步方法:

方案一:通用扩展方法(灵活适配多场景)

通过扩展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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 02:35:32