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

.NET Core WebAPI日志扩展方法:获取调用程序集名及指定路径存日志

实现.NET Core WebAPI日志扩展方法的两个需求

嘿,这个需求其实不难拆解,咱们一步步来搞定:

一、准确获取调用日志方法的程序集名称

要拿到真正调用日志方法的业务程序集(而不是日志扩展类或框架自身的程序集),直接用Assembly.GetCallingAssembly()可能会因为JIT优化或中间层干扰不准,所以我们用StackTrace遍历栈帧来过滤:

using System.Diagnostics;
using System.Reflection;

private static string GetCallingAppName()
{
    var stackTrace = new StackTrace();
    // 遍历栈帧,跳过日志相关程序集,找到第一个业务代码的调用方
    foreach (StackFrame frame in stackTrace.GetFrames())
    {
        var method = frame.GetMethod();
        if (method == null) continue;
        
        var declaringAssembly = method.DeclaringType.Assembly;
        var currentAssembly = Assembly.GetExecutingAssembly();
        
        // 排除当前扩展类所在程序集、微软日志框架和系统程序集
        if (declaringAssembly != currentAssembly && 
            !declaringAssembly.FullName.StartsWith("Microsoft.Extensions.Logging") &&
            !declaringAssembly.FullName.StartsWith("System."))
        {
            return declaringAssembly.GetName().Name;
        }
    }
    // 兜底方案:如果找不到,返回入口程序集名称
    return Assembly.GetEntryAssembly()?.GetName().Name ?? "UnknownApplication";
}

二、配置日志输出到指定路径

这里有两种方案:自定义日志提供程序(灵活可控),或者用成熟的日志框架(比如Serilog,更省心),我都给你列出来:

方案1:自定义日志提供程序(原生ILogger实现)

适合想要完全掌控日志逻辑的场景:

1. 写一个日志写入器

负责处理文件路径创建和日志写入:

public class AppSpecificFileLogger : ILogger
{
    private readonly string _categoryName;
    private readonly string _appName;
    private readonly string _logFilePath;

    public AppSpecificFileLogger(string categoryName, string appName)
    {
        _categoryName = categoryName;
        _appName = appName;
        
        // 构建目标路径:%windir%\log\{调用程序集名称}\logfile.log
        var windir = Environment.GetEnvironmentVariable("windir") ?? @"C:\Windows";
        var logDir = Path.Combine(windir, "log", _appName);
        
        // 确保目录存在,避免写入失败
        Directory.CreateDirectory(logDir);
        _logFilePath = Path.Combine(logDir, "logfile.log");
    }

    public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;

    public bool IsEnabled(LogLevel logLevel) => true; // 可根据需求调整日志级别过滤

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;

        // 拼接日志内容,包含调用程序集名称
        var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{logLevel}] [{_appName}] [{_categoryName}] {formatter(state, exception)}";
        if (exception != null)
        {
            logMessage += $"\nException Details: {exception.ToString()}";
        }

        // 异步写入,避免阻塞主线程
        _ = File.AppendAllTextAsync(_logFilePath, logMessage + Environment.NewLine);
    }
}
2. 写日志提供程序

负责创建日志实例,同时调用上面的方法获取调用程序集名称:

public class AppSpecificFileLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string categoryName)
    {
        var appName = GetCallingAppName();
        return new AppSpecificFileLogger(categoryName, appName);
    }

    public void Dispose() { }

    // 复用之前的GetCallingAppName方法
    private static string GetCallingAppName()
    {
        var stackTrace = new StackTrace();
        foreach (StackFrame frame in stackTrace.GetFrames())
        {
            var method = frame.GetMethod();
            if (method == null) continue;
            
            var declaringAssembly = method.DeclaringType.Assembly;
            var currentAssembly = Assembly.GetExecutingAssembly();
            if (declaringAssembly != currentAssembly && 
                !declaringAssembly.FullName.StartsWith("Microsoft.Extensions.Logging") &&
                !declaringAssembly.FullName.StartsWith("System."))
            {
                return declaringAssembly.GetName().Name;
            }
        }
        return Assembly.GetEntryAssembly()?.GetName().Name ?? "UnknownApplication";
    }
}
3. 在Program.cs中注册

把自定义提供程序加到日志系统:

var builder = WebApplication.CreateBuilder(args);

// 注册自定义日志提供程序
builder.Logging.AddProvider(new AppSpecificFileLoggerProvider());

// 其他WebAPI配置...
var app = builder.Build();
// ...

方案2:用Serilog(更简洁高效)

如果不想自己造轮子,Serilog的文件Sink可以轻松实现动态路径,性能也更优:

1. 安装NuGet包
Install-Package Serilog.AspNetCore
Install-Package Serilog.Sinks.File
2. 在Program.cs中配置
var builder = WebApplication.CreateBuilder(args);

// 先获取调用程序集名称
var appName = GetCallingAppName();
var windir = Environment.GetEnvironmentVariable("windir") ?? @"C:\Windows";
var logPath = Path.Combine(windir, "log", appName, "logfile.log");

// 配置Serilog
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.File(
        path: logPath,
        rollingInterval: RollingInterval.Day, // 按天滚动日志,可选
        outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u3}] [{AppName}] {Message:lj}{NewLine}{Exception}",
        restrictedToMinimumLevel: LogEventLevel.Information)
    .Enrich.WithProperty("AppName", appName)
    .CreateLogger();

// 替换默认日志系统
builder.Host.UseSerilog();

// 其他配置...

三、封装ILogger扩展方法(可选)

如果想给日志调用加个更友好的封装,比如直接调用logger.LogAppInfo("消息"),可以写个扩展类:

public static class LoggerExtensions
{
    public static void LogAppInfo(this ILogger logger, string message)
    {
        var appName = GetCallingAppName();
        logger.LogInformation("[{AppName}] {Message}", appName, message);
    }

    // 同样复用GetCallingAppName方法
    private static string GetCallingAppName()
    {
        var stackTrace = new StackTrace();
        foreach (StackFrame frame in stackTrace.GetFrames())
        {
            var method = frame.GetMethod();
            if (method == null) continue;
            
            var declaringAssembly = method.DeclaringType.Assembly;
            var currentAssembly = Assembly.GetExecutingAssembly();
            if (declaringAssembly != currentAssembly && 
                !declaringAssembly.FullName.StartsWith("Microsoft.Extensions.Logging") &&
                !declaringAssembly.FullName.StartsWith("System."))
            {
                return declaringAssembly.GetName().Name;
            }
        }
        return Assembly.GetEntryAssembly()?.GetName().Name ?? "UnknownApplication";
    }
}

关键注意事项

  • 性能影响:StackTrace会带来一定性能开销,高并发场景下建议缓存调用程序集名称,或者直接用入口程序集名称(如果不需要区分不同类库的调用)。
  • 权限问题:写入%windir%\log需要管理员权限,如果WebAPI运行在普通账户下,要么给目录加读写权限,要么换个路径(比如AppDomain.CurrentDomain.BaseDirectory\Logs)。
  • 异步写入:原生File.AppendAllTextAsync虽然是异步,但高并发下还是建议用Serilog这类自带缓冲和批量写入的框架,避免IO瓶颈。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 07:12:38