1

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

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

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

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

有任何想法吗?

4

1 回答 1

2

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

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

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 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;
        }
    }
}
于 2013-06-17T19:47:59.673 回答