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

NLog配置File目标时requestBegin的${scopeproperty:domainName}取值为空

问题原因
  • requestBegin目标获取不到值的核心逻辑:NLog的File目标默认会缓存首次计算得到的文件路径,如果第一次往这个目标写日志的时候${scopeproperty:domainName}是空,后续即使有了正确值,也不会重新计算路径,就会一直往空domain的路径写,看起来就是拿不到值。而errorsLogFile记录的Error日志都出现在请求处理的中后期,第一次写的时候domainName已经赋值完成,路径计算正确,所以运行正常。
  • 首次写入时domainName为空通常是两个场景导致的:
    • 应用启动时,Microsoft.AspNetCore.Hosting.Diagnostics会输出Info级别的宿主启动日志,其中部分日志包含starting关键字,会命中你的过滤规则,但这类日志没有任何请求上下文,domainName自然为空,会提前创建错误路径的日志文件。
    • 你给domainName赋值的Scope(一般是在自定义中间件里通过logger.BeginScope写入)注册位置太靠后,Kestrel收到请求后第一时间输出的"Request starting"日志,是在中间件管道外层触发的,此时还没进入你创建的Scope,拿不到对应值。
解决方法

按顺序操作即可解决问题:

  • 第一步:关闭File目标的文件路径缓存,强制每次写日志时重新计算文件名
    给两个File目标都加上keepFileOpen="false"配置,避免首次写入空路径后被固定:
    <target xsi:type="File" name="errorsLogFile" 
            fileName="c:\inor_logs\${scopeproperty:domainName}\error-${shortdate}.log"
            layout="" 
            keepFileOpen="false"/>
    
    <target xsi:type="File" name="requestBegin" 
            fileName="c:\inor_logs\${scopeproperty:domainName}\request-begin-${shortdate}.log"
            layout="" 
            keepFileOpen="false"/>
    
  • 第二步:优化日志过滤规则,排除无请求上下文的启动日志
    在现有过滤条件中增加请求方法非空的判断,把启动阶段没有请求信息的日志过滤掉,避免空值日志提前触发文件创建:
    <rules>
        <!--All logs, including from Microsoft-->
        <logger name="*" minlevel="Error" writeTo="errorsLogFile" />
    
        <logger name="Microsoft.AspNetCore.Hosting.Diagnostics" minlevel="Info" writeTo="requestBegin">
          <filters defaultAction="Ignore">
            <when condition="contains('${message}','starting') 
                             and '${aspnet-request-method}' != ''
                             and (contains('${uppercase:${aspnet-request-method}}','POST') 
                                  or contains('${uppercase:${aspnet-request-method}}','PUT') 
                                  or contains('${uppercase:${aspnet-request-method}}','DELETE'))" 
                  action="Log" />
          </filters>
        </logger>
      </rules>
    
  • 第三步:调整Scope的注册位置
    如果你是通过自定义中间件+BeginScope的方式写入domainName,必须把这个中间件放在请求管道注册的最顶端,也就是WebApplication实例创建后,第一个调用Use注册的中间件,要早于UseRouting、UseAuthentication、UseEndpoints等所有内置中间件,保证请求进入管道后第一时间就进入domainName的作用域。
  • 第四步(彻底规避Scope时机问题的方案):替换ScopeProperty为自定义布局渲染器
    如果调整中间件顺序后还是拿不到值(因为Kestrel的请求启动日志确实在中间件管道外层,中间件内创建的Scope无法覆盖到这个日志点),就不要用Scope传domainName了,直接写一个自定义布局渲染器从HttpContext实时取值,完全不依赖Scope生命周期:
    首先添加自定义渲染器的代码:
    using System.Text;
    using Microsoft.AspNetCore.Http;
    using NLog;
    using NLog.LayoutRenderers;
    using NLog.Web.LayoutRenderers;
    
    [LayoutRenderer("domain-name")]
    public class DomainNameLayoutRenderer : AspNetLayoutRendererBase
    {
        protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
        {
            HttpContext context = HttpContextAccessor.HttpContext;
            if (context == null)
            {
                return;
            }
            // 这里替换成你自己解析domainName的业务逻辑,和之前写入Scope的逻辑保持一致即可
            string domainValue = context.Request.Host.Host;
            builder.Append(domainValue);
        }
    }
    
    然后在Program.cs的启动最开头注册这个渲染器:
    using NLog;
    
    // 注册自定义布局渲染器
    LayoutRenderer.Register<DomainNameLayoutRenderer>("domain-name");
    
    var builder = WebApplication.CreateBuilder(args);
    // 后续原有服务注册、中间件注册逻辑保持不变
    
    最后把NLog配置里所有用到${scopeproperty:domainName}的地方替换成${domain-name}即可,这个方式只要有请求上下文就能拿到正确的domain值,完全不会受Scope注册时机影响。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 10:48:11