我们刚开始使用Log4Net(并且希望我们之前完成它)。虽然我们可以看到内部异常等,但是在记录异常时输出中似乎缺少的一件事是“Exception.Data”中保存的任何键/值信息。无论如何我们可以“开箱即用”吗?如果没有,因为我们真的只是刚刚开始寻找一种方法来实现这个功能?
作为示例,请参阅下面的基本伪代码。我们不想用上下文信息污染异常消息只是问题所在(我们可能已经丢失了更多有助于调查实际问题的数据信息)。但是现在我们在日志中看到的只是异常类型,消息,任何堆栈跟踪 - 但没有异常“数据”。这意味着在我们的日志中我们丢失了客户ID等。我们如何轻松地将这些信息输入到我们的日志中(无需在每个异常捕获中手动编写代码)。
try
{
var ex = new ApplicationException("Unable to update customer");
ex.Data.Add("id", customer.Id);
throw ex;
}
catch(ApplicationException ex)
{
logger.Error("An error occurred whilst doing something", ex);
throw;
}
答案 0 :(得分:19)
继Stefan的领导之后:
namespace YourNamespace {
public sealed class ExceptionDataPatternConverter : PatternLayoutConverter {
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent) {
var data = loggingEvent.ExceptionObject.Data;
if (data != null) {
foreach(var key in data.Keys) {
writer.Write("Data[{0}]={1}" + Environment.NewLine, key, data[key]);
}
}
}
}
}
在您的配置中添加%ex_data和转换器:
<appender ...>
...
<layout type="log4net.Layout.PatternLayout,log4net">
<conversionPattern value="%date %d{HH:mm:ss.fff} [%t] %-5p %c %l - %m%n %ex_data"/>
<converter>
<name value="ex_data" />
<type value="YourNamespace.ExceptionDataPatternConverter" />
</converter>
</layout>
答案 1 :(得分:6)
如果您定义了多个appender,则可以使用自定义渲染器,而不是为每个布局定义转换器。
网络/ app.config中强>
<log4net>
...
<renderer renderingClass="YourNamespace.ExceptionObjectLogger, YourAssembly" renderedClass="System.Exception" />
...
</log4net>
<强> ExceptionObjectLogger 强>
public class ExceptionObjectLogger : IObjectRenderer
{
public void RenderObject(RendererMap rendererMap, object obj, TextWriter writer)
{
var ex = obj as Exception;
if (ex == null)
{
// Shouldn't happen if only configured for the System.Exception type.
rendererMap.DefaultRenderer.RenderObject(rendererMap, obj, writer);
}
else
{
rendererMap.DefaultRenderer.RenderObject(rendererMap, obj, writer);
const int MAX_DEPTH = 10;
int currentDepth = 0;
while (ex != null && currentDepth <= MAX_DEPTH)
{
this.RenderExceptionData(rendererMap, ex, writer, currentDepth);
ex = ex.InnerException;
currentDepth++;
}
}
}
private void RenderExceptionData(RendererMap rendererMap, Exception ex, TextWriter writer, int depthLevel)
{
var dataCount = ex.Data.Count;
if (dataCount == 0)
{
return;
}
writer.WriteLine();
writer.WriteLine($"Exception data on level {depthLevel} ({dataCount} items):");
var currentElement = 0;
foreach (DictionaryEntry entry in ex.Data)
{
currentElement++;
writer.Write("[");
ExceptionObjectLogger.RenderValue(rendererMap, writer, entry.Key);
writer.Write("]: ");
ExceptionObjectLogger.RenderValue(rendererMap, writer, entry.Value);
if (currentElement < dataCount)
{
writer.WriteLine();
}
}
}
private static void RenderValue(RendererMap rendererMap, TextWriter writer, object value)
{
if (value is string)
{
writer.Write(value);
}
else
{
IObjectRenderer keyRenderer = rendererMap.Get(value.GetType());
keyRenderer.RenderObject(rendererMap, value, writer);
}
}
}
答案 2 :(得分:5)
我认为解决此问题的更多log4net方法是编写PatternLayoutConverter
。可以找到一个示例here。
在转换方法中,您可以像这样访问您的数据(并按照您喜欢的方式编写):
override protected void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
var data = loggingEvent.ExceptionObject.Data;
}
答案 3 :(得分:0)
您可以为记录器创建一个Extension方法来记录客户ID:您不应该向异常添加重要信息
您可以抽象“附加信息记录”的概念,并使用返回您要记录的其他信息的方法创建一个接口
public interface IDataLogger
{
string GetAdditionalInfo();
}
public class UserDataLogger: IDataLogger
{
public string GetAdditionalInfo()
{
return "UserName";
}
}
public class MoreDataLogger : IDataLogger
{
public string GetAdditionalInfo()
{
return "something";
}
}
您可以创建不同的“数据记录器”,也可以将它们组合在一起
然后你可以创建一个获得记录器类型的通用扩展方法
public static class ExLog4Net
{
public static void Error<T>(this ILog log, Exception ex) where T:IDataLogger,new()
{
var dataLogger=new T();
log.Error(ex.ToString() + " " + dataLogger.GetAdditionalInfo());
}
}
您可以执行以下操作:
try
{
}
catch (Exception ex)
{
logger.Error<UserDataLogger>(ex);
logger.Error<MoreDataLogger>(ex);
throw;
}
答案 4 :(得分:0)
我认为Massimiliano有正确的想法,但我会略微修改他的解决方案。
如果您计划在异常中将所有其他数据保留在字典Data
中,我会将其扩展方法更改为以下内容:
public static class ExLog4Net
{
public static void Error(this ILog log, Exception ex)
{
StringBuilder formattedError = new StringBuilder();
formattedError.AppendFormat("Exception: {0}\r\n", ex.ToString());
foreach (DictionaryEntry de in ex.Data)
formattedError.AppendFormat("{0}: {1}\r\n", de.Key, de.Value);
log.Error(formattedError.ToString());
}
}
然后,您可以将此方法扩展保留在您将在所有应用程序中使用的库中。如果您没有,则必须将其添加到每个项目中。