日志信息最佳实践

4

我正在进行生产支持,很大程度上依赖日志来进行故障排查。我发现目前的日志信息非常混乱。

你能提供编写日志信息的最佳实践或指南吗?

顺便说一下:我们正在使用log4Net。你有关于替代库的建议吗?

谢谢。

4个回答

7
理想情况下,您的日志消息应包括何时、什么、在哪里、谁触发了消息以及一些指示触发消息的事件有多严重的详细信息。
  • 包括日期和时间。如果您的应用程序跨越多个时区,请同时包括时区指示器。如果每个人都知道03:11:04在他们的时区是什么时间,这将消除混乱。
  • 包括日志记录严重性级别。
  • 在日志消息中包括触发日志消息的模块或类的某些指示。
  • 如果可能,请鼓励开发人员在消息中包含特定信息:例如,“文件损坏”比“文件损坏:“C:\foo\bar.dat””不太有用。
  • 如果可能,请开发人员在错误消息中包括某种会话或事务ID。能够过滤具有错误的事务的消息并忽略所有正常的事务非常方便。
  • 通常最好在日志消息中包括错误代码。

我赞同@Oded提供的保持消息整洁的建议。对于例行工作,如日期和时间、日志级别、错误代码,我会尝试将它们格式化为固定宽度,并将它们放在开头。这使得扫描日志变得更加容易。

关于日志消息格式的良好指南,我遇到的唯一一件事是《发布它!》第17章:http://www.pragprog.com/titles/mnee/release-it 上述大部分建议都基于此书。


1
这本书看起来非常有用。 - Ricky

2
@corriganjc有很多好的建议。
我想补充一些具体内容:如果可能的话,请考虑使用UTC记录消息。一方面,查看生成的消息并记住正确的偏移量以“正确”地解释它们可能会很烦人。另一方面,所有日志消息都可以按时间/日期排序,无需进一步解释。(如果您记录了来自两个时区的消息,并且已经使用“本地”时间记录了它们,则在将它们带到公共时区之前,无法对它们进行排序)。
使用GlobalContext、ThreadContext和LogicalThreadContext对象将附加上下文注入到您的消息中。记录像“会话ID”或“事务ID”这样的东西是一个好主意,最好是使用“上下文”对象来完成,而不是通过显式添加这些值到实际的记录调用站点。您只需设置上下文一次,然后通过添加格式选项,就可以在每条消息中记录该上下文。
如果使用上下文对象,请考虑制定“标准”值名称,甚至定义可以被开发人员引用的字符串常量,以便他们在尝试添加其上下文时不必犯错。
//Context value names
public static class DiagnosticContextValueNames
{
  public static string TransactionId = "transactionid";
  public static string SessionId = "sessionid";
}

//In your code
log4net.ThreadContext.Properties[DiagnosticContextValueNames.TransactionId] = GetTransactionId();
log4net.ThreadContext.Properties[DiagnosticContextValueNames.SessionId] = GetSessionId();

//Somewhere later on...
logger.Info("hello");  // this message can be tagged with the transaction id and session id if you use the appropriate formatting options

甚至可以考虑在GlobalContext.Properties、ThreadContext.Properties等上实现扩展方法,以帮助指导开发人员正确设置上下文值:
public static class LoggingExtensions
{
  public static void SetTransactionId(this ThreadContextProperties props, string trans)
  {
    props["TransactionId"] = trans;

    // Or, using constants as defined above...
    props[ThreadContextValueNames.TransactionId] = trans;
  }
}


// In your code...
log4net.ThreadContext.Properties.SetTransactionId(GetTransactionId());

// As compared to this:
log4net.ThreadContext.Properties["transactionid"] = GetTransactionId();

如果您包装或继承log4net记录器,可以自动添加一些上下文信息,从而减轻开发人员的负担。包装或继承log4net记录器的正确方法并不复杂。关键是将您包装的记录器类型传递给log4net。cfeduke在this post中的回答提供了一种包装log4net记录器的方法。
您可以遵循他的例子,但是使用“ILogger.Log”方法实现所有日志记录调用。在“Log”内部,您可以添加要为所有日志消息添加的属性。
// This approach requires more effort than simply populating the context properties "normally", and
// is probably overkill for most situations.  However, it can prove useful if you are able
// to have access to the context information that you want to log from within the Log method
// of the logger.
public void Log(type loggerBoundaryDeclaringType, LogLevel level, object message, object exception)
{
  log4net.ThreadContext.Properties["transactionid"] = GetTransactionId();
  log4net.ThreadContext.Properties["sessionid"] = GetSessionId();
  _logger.Log(loggerBoundaryDeclaringType, level, message, exception);
}

关于其他日志平台,您可以看看NLog。最近发布了一个新版本作为Beta版本。它具有许多与log4net类似的功能。
您还可以考虑使用老牌的System.Diagnostics.TraceSource。如果您选择这个路线,请查看codeplex上的Ukadc.Diagnostics。这是一个针对System.Diagnostics的附加库,提供了丰富的消息格式化功能(类似于log4net和NLog)。Ukadc.Diagnostics的一个好处是它仅依赖于配置。您不必采取源或引用依赖项来使用它。

2

ELMAH 是一个非常好的日志记录库,也是log4net不错的替代品。

关于日志格式(您说信息“混乱”,但没有解释具体意思) - 确保每个条目与其他条目清晰分开,并以可读的方式格式化(间距、换行等...)。


1
> ... we're using log4Net. Do you have any suggestion on alternative library? 

你可以使用 common.logging(http://netcommon.sourceforge.net),它是一个与以下日志记录器一起使用的小型日志记录包装器:

  • log4net
  • nlog
  • Enterprise Library Logging

这使得日志记录配置变得更加复杂,因为您必须配置要使用的日志记录引擎,并且还必须配置日志记录引擎。

其日志记录 API 受 log4net-api 启发。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接