Подтвердить что ты не робот

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

Мы только начинаем работу с 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;
}
4b9b3361

Ответ 1

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

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

override protected void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
   var data = loggingEvent.ExceptionObject.Data;
}

Ответ 2

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

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>

Ответ 3

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

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);
        }
    }
}

Ответ 4

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

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

 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;
        }

Ответ 5

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

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

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