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;
}
Ответы
Ответ 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());
}
}
Затем вы будете придерживаться этого расширения метода в библиотеке, которую вы будете использовать во всех ваших приложениях. Если у вас его нет, вам нужно добавить его в каждый проект.