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

如何在类库方法记录异常时获取调用项目/程序集名称?

如何获取NuGet包调用项目名称及重构建议

我们团队现在用自定义NuGet包拆分ASP.NET Core 6 Web项目的通用代码,比如员工查询、工具类这类包。每个NuGet包的方法都会自行处理异常,把错误细节记录到数据库里,但现在有个头疼的问题——日志里只能看到NuGet包自身的信息(比如Location: Namespace.Employee -> EmpClass.cs --> methodName(parameter)),根本分不清是哪个Web项目调用时出的错,只能知道是NuGet包抛了异常。

这些辅助类都是静态类,如果要加项目名称参数,要么复制几十个方法,要么改现有方法导致旧项目直接报错(我们靠版本管理不想立刻改旧项目)。

我试过在NuGet包里用这段代码获取调用程序集,但结果总是指向NuGet包自己的DLL:

string callingAssembly = "Calling Assembly Not Available";
if( Assembly.GetCallingAssembly() != null )
{
    callingAssembly = Assembly.GetCallingAssembly().ToString();
}

想请教两个问题:

  1. 现在这种环境下,能不能获取到实际调用的Web项目名称?
  2. 从抽象设计层面,怎么重构才能让这类变更在未来更容易实现?

补充代码示例

调用端代码(Web项目里):

//User info是来自NuGet包的类库,DLL名为Intranet.Users。
UserInfo crashTest = UserInfo.GetUser(555);

NuGet包Intranet.Users中User.cs的方法:

public static UserInfo GetUser(int UserId)
{
    try
    {
        //查询UserId=555的用户数据的代码
    }
    catch (Exception ex)
    {
        string callingAssembly = "Calling Assembly Not Available";
        if( Assembly.GetCallingAssembly() != null )
        {
            callingAssembly = Assembly.GetCallingAssembly().ToString();
        }

        //Logging类来自Intranet.Utilities NuGet包,用于将错误记录到数据库。
        Logging.LogError(ex, "Intranet.Users > Users.cs > GetUser(UserId)", "UserId = " + UserId.ToString() +  $" in {callingAssembly}" );
    }
}

这时callingAssembly的值是"Intranet.Users, Version=x.x.x.x, Culture=neutral, PublicKeyToken=null",完全不是我们要的调用项目名称。


一、获取调用项目名称的可行方案

Assembly.GetCallingAssembly()之所以返回NuGet包自身,是因为JIT优化或者方法内联的原因,尤其是静态方法的调用,可能会让调用栈被优化。这里有两个可靠的解决思路:

1. 调用栈追踪法

通过遍历调用栈帧,跳过当前NuGet包的程序集,找到第一个外部调用的项目程序集:

using System.Diagnostics;
using System.Reflection;

public static string GetCallingProjectName()
{
    var stackTrace = new StackTrace();
    foreach (var frame in stackTrace.GetFrames())
    {
        var method = frame.GetMethod();
        var assembly = method.DeclaringType.Assembly;
        // 替换成你的NuGet包程序集前缀,排除自身
        if (!assembly.FullName.StartsWith("Intranet.Users"))
        {
            return assembly.GetName().Name;
        }
    }
    return "Calling Project Not Found";
}

注意:Release模式下如果开启了代码优化,部分调用帧可能会被省略。可以给这个方法加上[MethodImpl(MethodImplOptions.NoInlining)]标记,或者在NuGet包项目文件中添加<DebugType>full</DebugType>来避免内联优化。

2. 全局上下文注入法(更高效可靠)

利用ASP.NET Core的IWebHostEnvironment获取项目名称,让Web项目在启动时把这个值注入到NuGet包的静态上下文里:

  • 在NuGet包中新增静态辅助类:
public static class AppContextHelper
{
    public static string CurrentApplicationName { get; set; } = "Unknown";
}
  • 在每个Web项目的Program.cs中初始化:
var app = builder.Build();
AppContextHelper.CurrentApplicationName = app.Services.GetRequiredService<IWebHostEnvironment>().ApplicationName;

这种方式不受JIT优化影响,性能更好,只需要每个Web项目做一次初始化配置,后续NuGet包的方法直接读取AppContextHelper.CurrentApplicationName即可。


二、重构建议——让未来变更更易实现

静态类的设计虽然简单,但扩展性极差,遇到需要上下文信息的场景就会陷入两难。推荐从以下几个方向重构:

1. 改用依赖注入(DI)模式(最优方案)

放弃静态类,把NuGet包的功能改成可注入的服务,通过构造函数获取上下文信息:

  • NuGet包中定义接口和实现:
public interface IUserService
{
    UserInfo GetUser(int userId);
}

public class UserService : IUserService
{
    private readonly string _applicationName;

    // 注入ASP.NET Core的环境信息,直接拿到项目名称
    public UserService(IWebHostEnvironment env)
    {
        _applicationName = env.ApplicationName;
    }

    public UserInfo GetUser(int userId)
    {
        try
        {
            // 查询逻辑
        }
        catch (Exception ex)
        {
            Logging.LogError(ex, "Intranet.Users > Users.cs > GetUser(UserId)", 
                $"UserId = {userId} in {_applicationName}");
        }
    }
}
  • Web项目中注册服务:
builder.Services.AddScoped<IUserService, UserService>();

这种方式彻底解决了静态类的扩展性问题,未来要添加任何上下文信息,都可以通过DI注入,不需要修改方法签名,也不会影响旧项目(只要旧项目不升级NuGet包版本即可)。

2. 上下文参数过渡方案(兼容旧项目)

如果暂时无法全量切换到DI,可以定义一个上下文对象,给方法添加可选参数:

public class CallContext
{
    public string ApplicationName { get; set; } = "Unknown";
}

// 保留原有方法,兼容旧项目
public static UserInfo GetUser(int userId)
{
    return GetUser(userId, null);
}

// 新增带上下文的方法,供新项目使用
public static UserInfo GetUser(int userId, CallContext context)
{
    var appName = context?.ApplicationName ?? GetCallingProjectName(); // 降级到调用栈方式
    try
    {
        // 查询逻辑
    }
    catch (Exception ex)
    {
        Logging.LogError(ex, "Intranet.Users > Users.cs > GetUser(UserId)", 
            $"UserId = {userId} in {appName}");
    }
}

旧项目继续使用无参数方法,新项目可以传递上下文,逐步过渡到更优的设计。

3. 统一日志上下文

如果使用ASP.NET Core自带的ILogger日志系统,可以在Web项目中设置日志作用域:

var env = builder.Environment;
using (logger.BeginScope("ApplicationName={Name}", env.ApplicationName))
{
    // 项目启动后的逻辑
}

NuGet包中也改用ILogger替代自定义Logging类,日志会自动带上这个作用域信息,不需要额外传递参数,这也是ASP.NET生态推荐的日志追踪方式。

内容的提问来源于stack exchange,提问作者jason

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 17:06:51