如何设置未绑定到特定线程的非全局NLog自定义布局渲染器?

时间:2013-06-17 19:03:13

标签: nlog

我有一个工作引擎,它并行运行多个工作。这些工作本身可能是多线程的。

我希望通过自定义布局渲染器公开特定于作业的信息。到目前为止,我看到的解决方案建议使用GDC,NDC或MDC设施。例如 - http://nlog-forum.1685105.n2.nabble.com/Custom-Layout-Renderers-Runtime-Values-td4065731.html

这种方法并不好,因为我想要公开的信息是每个作业,既不是全局的,也不是本地的线程。作业执行可能涉及来自线程池和/或显式创建的不同线程。

我希望尽可能少地更改现有的工作代码。例如,我知道我可能需要更改日志实例的获取方式或生活范围(实例与静态),但我当然不想更改日志消息。

有什么想法吗?

1 个答案:

答案 0 :(得分:2)

如果您愿意使用Logger的Log方法,那么您可以创建一个LogEventInfo并将额外的作业特定值存储在LogEventInfo的属性中(类似于GDC和MDC,除了每个LogEventInfo都有自己的实例)。 / p>

因此,您的代码可能如下所示:

void RunJob(object job)
{
  string name;
  int id;
  DateTime started;

  GetSomeParametersFromJob(job, out name, out id, out started);

  var le = new LogEventInfo(LogLevel.Info, logger.Name, "Hello from RunJob");
  le.Properties.Add("JobName", name);
  le.Properties.Add("JobId", id);
  le.Properties.Add("JobStarted", started);

  logger.Log(le);
}

可以清除日志记录调用,因为它不那么详细,但是你明白了。只需将所需参数添加到LogEventInfo类的Properties字典中,然后可以使用EventContext布局渲染器将这些值记录下来。您可以将其配置为:

<targets>
    <target name="file" xsi:type="File" layout="${longdate} | ${level} | ${logger} | JobName = ${event-context:JobName} | JobId = ${event-context:JobId} | JobStarted = ${event-context:JobStarted} | ${message}" fileName="${basedir}/${shortdate}.log" />
</targets>

您可以使用“变量”清理配置:

<variable name="JobName" value = "${event-context:JobName}" />
<variable name="JobId" value = "${event-context:JobId}" />
<variable name="JobStarted" value = "${event-context:JobStarted}" />
<variable name="JobLayout" value="${longdate} | ${level} | ${logger} | ${JobName} | ${JobId} | ${JobStarted} | $ ${message}"/>

<targets>
  <target name="file" 
          xsi:type="File" 
          layout="${JobLayout}" 
          fileName="${basedir}/${shortdate}.log" />
</targets>

更新

这是另一个想法......您可以编写自己的“上下文”对象(类似于GDC和MDC),该对象由识别您的工作的东西键入。您可能希望使用CallContext来保存额外的参数,因为可以将值放入CallContext,以便它们“流动”到子线程。然后,如果要将值放在作业运行器的上下文中,您不希望它们流向所有子线程,您只希望它们流向正在运行的作业。所以,也许要开始他们可以被放入全球数据,但这可能会导致瓶颈......无论如何......这有什么用呢?

这一切都非常粗糙,但我认为它传达了这个想法。这是不是一个好主意?我会让你成为法官。从好的方面来说,您的日志记录站点没有变化,您可以设置“上下文”参数,而不必花费比使用GDC / MDC时更多的努力。在负面,有一些代码要写,在访问全局字典时可能存在瓶颈。

创建自己的全局字典(类似于NLog的GlobalDiagnosticContext here https://github.com/NLog/NLog/blob/master/src/NLog/GlobalDiagnosticsContext.cs)。使Add API具有GUID类型的额外参数。因此,在开始作业之前存储代码的参数可能如下所示:

string name;
int id;
DateTime started;

GetSomeParametersFromJob(job, out name, out id, out started);

GUID jobActivity = Guid.NewGuid();
JobRunnerNamespace.JobContext.Add(jobActivity, "JobName", name);
JobRunnerNamespace.JobCotnext.Add(jobActivity, "JobId", id);
JobRunnerNamespace.JobContext.Add(jobActivity, "JobStarted", started);

job.Activity = jobActivity;

job.Run();

在作业内部,当它启动时,它将System.Diagnostics.CorrelationManager.ActivityId设置为输入guid:

public class MyJob
{
  private Guid activityId;

  public Guid
  {
    set 
    {
      activityId = value;
    }
    get
    {
      return activityId;
    }
  }

  public void Run()
  {
    System.Diagnostics.CorrelationManager.ActivityId = activityId;

    //Do whatever the job does.

    logger.Info("Hello from Job.Run.  Hopefully the extra context parameters will get logged!");
  }
}

JobContextLayoutRenderer看起来像这样:

[LayoutRenderer("JobContext")]
public class JobContextLayoutRenderer : LayoutRenderer
{
    [RequiredParameter]
    [DefaultParameter]
    public string Item { get; set; }

    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
        Guid activity = System.Diagnostics.CorrelationManager.ActivityId;

        string msg = JobContext.Get(activity, this.Item);
        builder.Append(msg);
    }
}

JobContext看起来像这样(我们现在只考虑Set和Get):

public static class JobContext
{
    private static Dictionary<Guid, Dictionary<string, string>> dict = new Dictionary<Guid, Dictionary<string, string>>();

    public static void Set(Guid activity, string item, string value)
    {
        lock (dict)
        {
            if (!dict.ContainsKey(activity))
            {
              dict[activity] = new Dictionary<string, string>();
            }
            var d = dict[activity];
            lock (d) //Might need to lock this dictionary
            {
              d[activity][item] = value;
            }
        }
    }

    /// <summary>
    /// Gets the Global Diagnostics Context named item.
    /// </summary>
    /// <param name="item">Item name.</param>
    /// <returns>The item value of string.Empty if the value is not present.</returns>
    public static string Get(Guid activity, string item)
    {
        lock (dict)
        {
            string s = string.Empty;

            var d = dict.TryGetValue(activity, d);
            if (d != null)
            {
              lock(d) //Might need to lock this dictionary as well
              {
                if (!d.TryGetValue(item, out s))
                {
                  s = string.Empty;
                }
              }
            }

            return s;
        }
    }
}