我有一个工作引擎,它并行运行多个工作。这些工作本身可能是多线程的。
我希望通过自定义布局渲染器公开特定于作业的信息。到目前为止,我看到的解决方案建议使用GDC,NDC或MDC设施。例如 - http://nlog-forum.1685105.n2.nabble.com/Custom-Layout-Renderers-Runtime-Values-td4065731.html
这种方法并不好,因为我想要公开的信息是每个作业,既不是全局的,也不是本地的线程。作业执行可能涉及来自线程池和/或显式创建的不同线程。
我希望尽可能少地更改现有的工作代码。例如,我知道我可能需要更改日志实例的获取方式或生活范围(实例与静态),但我当然不想更改日志消息。
有什么想法吗?
答案 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;
}
}
}