如果您愿意使用 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;
}
}
}