Logging Exception.Data с использованием Log4Net

17

Мы только начинаем работу с Log4Net (и желаем, чтобы мы это сделали раньше). Хотя мы можем видеть внутренние исключения и т. Д., Одна вещь, которая, кажется, отсутствует на выходе при регистрации исключения, - это любая информация о ключе / значении, хранящаяся в «Исключении.Data». В любом случае мы можем сделать это «из коробки»? Если нет, поскольку мы действительно только начинаем, где нужно искать способ реализовать эту функциональность?

В качестве примера, пожалуйста, см. самый простой псевдо-код ниже. Мы не хотим загрязнять сообщение об исключении контекстной информацией только тем, что проблема (мы, вероятно, потеряли бы больше информации в данных, которые помогут в расследовании реальной проблемы). Но сейчас все, что мы видим в наших журналах, это тип исключения, сообщение, любая трассировка стека, но не исключение «данные». Это означает, что в наших журналах мы теряем идентификатор клиента и т. Д. Как мы можем легко получить эту информацию в наших журналах (без необходимости кодировать ее вручную в каждом исключении исключения).

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;
}
    
задан Paul Hadfield 04.08.2011 в 17:30
источник

5 ответов

5

Я думаю, что более log4net-подход к этой проблеме - написать PatternLayoutConverter . Пример можно найти .

В методе преобразования вы можете получить доступ к своим данным следующим образом (и написать его так, как вам нравится):

override protected void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
   var data = loggingEvent.ExceptionObject.Data;
}
    
ответ дан Stefan Egli 05.08.2011 в 12:20
18

Следуя примеру Стефана:

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>

    

ответ дан Jeroen K 17.10.2011 в 11:16
  • Я реализовал ваше решение и работает как шарм. Благодаря! –  Ignacio Soler Garcia 07.05.2012 в 00:11
  • PatternLayout также по умолчанию не предполагает, что никакое исключение не отображается, поэтому не забудьте сообщить, что вы собираетесь отображать исключение: <IgnoresException value="False" /> в разделе Layout, иначе ваш appender может сделать это для вы так, как он хочет –  Denis 14.03.2014 в 17:25
6

Если у вас установлено несколько приложений, вы можете использовать собственный рендер, а не определять конвертер для каждого макета.

Web / 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);
        }
    }
}
    
ответ дан Daniel Ballinger 18.02.2015 в 22:25
  • Мне очень нравится эта идея, но у вашего решения проблемы с внутренними исключениями. Поскольку вы используете rendererMap.DefaultRenderer.RenderObject (rendererMap, obj, writer); в цикле он отобразит исключение-стек умножить количество стековых исключений. Я предлагаю изменить вашу реализацию, чтобы она работала так, как должна. –  SeriousM 27.10.2015 в 15:58
0

Я думаю, что Массимилиано имеет правильную идею, но я бы немного изменил его решение.

Если вы планируете прикреплять все свои дополнительные данные в словаре 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());
    }
 }

Затем вы будете придерживаться этого расширения метода в библиотеке, которую вы будете использовать во всех ваших приложениях. Если у вас его нет, вам нужно добавить его в каждый проект.

    
ответ дан Cole W 04.08.2011 в 20:45
0

вы можете создать метод расширения для вашего регистратора для регистрации идентификатора клиента: вы не должны добавлять важную информацию в исключение

Вы можете абстрагировать концепцию «Дополнительная информация для регистрации» и создать интерфейс с методом, который возвращает дополнительную информацию, которую вы хотите зарегистрировать

 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;
        }
    
ответ дан Massimiliano Peluso 04.08.2011 в 17:45
  • Спасибо, я боюсь, что это только переместит ручную работу в метод расширения, в то же время потеряв большую выгоду от передачи исключения в метод log4net. Каждый раз, когда у нас были какие-то данные, мы хотели бы написать модифицированный метод расширения и т. Д. –  Paul Hadfield 04.08.2011 в 19:53
  • это всего лишь «черновик» ... Я опубликую лучшее решение, но идея разделена на информационную запись из журнала регистрации исключений –  Massimiliano Peluso 04.08.2011 в 20:49
  • Я отредактировал свой ответ –  Massimiliano Peluso 05.08.2011 в 11:55