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

在OnActionExecutedAsync中添加的NLog Scope属性未在日志中渲染

问题分析与解决

问题现象

在ASP.NET Web API的ActionFilterAttribute中,通过ScopeContext.PushProperty在OnActionExecutedAsync方法添加的NLog范围属性未在日志中渲染显示;而在OnActionExecutingAsync方法中添加的同类属性可正常显示。

相关代码

ActionFilterAttribute代码

/// <summary>
/// 执行Action方法前
/// </summary>
public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
     ScopeContext.PushProperty("Any-Executing-Property", "Value");
     return base.OnActionExecutingAsync(actionContext, cancellationToken);
}

/// <summary>
/// 执行Action方法后
/// </summary>
public override Task OnActionExecutedAsync(HttpActionExecutedContext ctx, CancellationToken cancellationToken)
{
     ScopeContext.PushProperty("Any-Executed-Property", "Value");
     return base.OnActionExecutedAsync(ctx, cancellationToken);
}

NLog.config配置

<target name="jsonFile"
        xsi:type="File"
        fileName="${logDirectory}/api.jsonl"
        archiveFileName="${logDirectory}/archives/api{#####}.jsonl"
        archiveAboveSize="1000240" 
        archiveNumbering="Sequence" 
        maxArchiveFiles="1"
        keepFileOpen="true" 
        concurrentWrites="false"
        encoding="UTF-8">
    <layout xsi:type="JsonLayout"
        includeAllProperties="true"
        MaxRecursionLimit="2" 
        suppressSpaces="true"
        includeScopeProperties="true">
        <attribute name="context" encode="false">
        <layout type="JsonLayout">
            <attribute name="Any-Executing-Property" layout="${scopeproperty:item=Any-Executing-Property}"/>    
            <attribute name="Any-Executed-Property" layout="${scopeproperty:item=Any-Executed-Property}"/>          
        </layout>
        </attribute>
    </layout>
    </target>

实际日志结果

{"context":{ "Any-Executing-Property": "Value" },"Any-Executing-Property":"Value"}

原因解释

ASP.NET Web API的过滤器执行流程决定了:

  • OnActionExecutingAsync在Action方法执行前触发,此时添加的Scope属性会被包含在后续整个Action执行过程中输出的日志上下文里,所以能正常显示。
  • OnActionExecutedAsync在Action方法执行完成后触发,这时候Action内部的日志已经全部输出完毕,新添加的Scope属性无法回溯到之前的日志中;如果不在OnActionExecutedAsync内部主动记录日志,这个属性就没有对应的日志条目可以附着,自然不会出现在现有日志里。

解决方案

方案1:在OnActionExecutedAsync内部记录日志

如果需要让Any-Executed-Property出现在日志中,可在添加属性后主动记录一条日志,此时Scope属性会生效:

private static readonly ILogger _logger = LogManager.GetCurrentClassLogger();

/// <summary>
/// 执行Action方法后
/// </summary>
public override Task OnActionExecutedAsync(HttpActionExecutedContext ctx, CancellationToken cancellationToken)
{
     using (ScopeContext.PushProperty("Any-Executed-Property", "Value"))
     {
         _logger.Info("Action执行完成");
         return base.OnActionExecutedAsync(ctx, cancellationToken);
     }
}

此时日志会包含该属性:

{"context":{ "Any-Executing-Property": "Value", "Any-Executed-Property": "Value" },"Any-Executing-Property":"Value","Any-Executed-Property":"Value","message":"Action执行完成"}

方案2:提前收集属性信息,在Action执行时注入Scope

如果需要让Action内部的日志也包含Any-Executed-Property相关信息,可在OnActionExecutingAsync中提前准备好后续需要的属性数据,通过HttpActionContext.Properties存储,然后在Action内部或其他日志输出点将其添加到Scope中:

public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
     ScopeContext.PushProperty("Any-Executing-Property", "Value");
     // 提前存储后续需要的属性值
     actionContext.Properties["ExecutedPropertyValue"] = "Value";
     return base.OnActionExecutingAsync(actionContext, cancellationToken);
}

// 在Action方法中
public IHttpActionResult Get()
{
    if (Request.Properties.TryGetValue("ExecutedPropertyValue", out var value))
    {
        using (ScopeContext.PushProperty("Any-Executed-Property", value.ToString()))
        {
            _logger.Info("Action执行中");
        }
    }
    return Ok();
}

方案3:使用NLog的全局诊断上下文(GDC)替代ScopeContext

如果属性不需要严格的异步上下文隔离,可使用GlobalDiagnosticsContext.Set来设置属性,它的生命周期更长,能覆盖到OnActionExecutedAsync阶段的日志:

public override Task OnActionExecutedAsync(HttpActionExecutedContext ctx, CancellationToken cancellationToken)
{
     GlobalDiagnosticsContext.Set("Any-Executed-Property", "Value");
     try
     {
         _logger.Info("Action执行完成");
         return base.OnActionExecutedAsync(ctx, cancellationToken);
     }
     finally
     {
         // 记得清理,避免污染后续请求
         GlobalDiagnosticsContext.Remove("Any-Executed-Property");
     }
}

同时需要修改NLog配置中的布局,将${scopeproperty}替换为${gdc}:

<attribute name="Any-Executed-Property" layout="${gdc:item=Any-Executed-Property}"/>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 22:57:27