使用Log4Net记录Exception.Data

时间:2011-08-04 15:30:36

标签: exception log4net

我们刚开始使用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;
}

5 个答案:

答案 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());
    }
 }

然后,您可以将此方法扩展保留在您将在所有应用程序中使用的库中。如果您没有,则必须将其添加到每个项目中。