Сегодня я бы хотел поделиться своей болью: архитектурный изъян в реализации метода Exception.ToString() приводит к потере части информации для некоторых вложенных исключений. Всем известно, что данный метод выводит Message + StackTrace самого исключения и всех вложенных. Например, следующий код:
Это касается не только FileNotFoundException. Любая информация, не содержащаяся в Message или StackTrace как стандартных исключений .NET, так и ваших собственных, будет утеряна - если использовать стандартный ToString() для логирования. Не поможет даже переопределение метода ToString() в вашем исключении (что, впрочем, в любом случае необходимо сделать, если вы храните информацию в полях, отличных от Message). Взгляните на реализацию ToString() у класса Exception (я привожу сокращенный вариант, а полную версию можно найти в официальных исходных кодах .NET):
Похоже, единственный надежный на 100% вариант - гарантировать вызов публичного ToString() для каждого вложенного исключения через метод-расширение:
Полезные ссылки:
Why doesn't System.Exception.ToString call virtual ToString for inner exceptions? - пост на StackOverflow с похожей болью;
О логировании исключений и Exception.ToString - аналогичные проблемы при логировании AggregationException.
var exception = new FileNotFoundException("error message", "filename");
Console.WriteLine(exception.ToString());
Выведет на консоль: System.IO.FileNotFoundException: error messageТеперь вложим наше исключение в другое:
File name: 'filename'
var exception = new FileNotFoundException("error message", "filename");
var outerException = new Exception("outer message", exception);
Console.WriteLine(outerException.ToString());
Результат:System.Exception: outer message ---> System.IO.FileNotFoundException: error messageЗаметили? Где имя пропавшего файла?
--- End of inner exception stack trace ---
Это касается не только FileNotFoundException. Любая информация, не содержащаяся в Message или StackTrace как стандартных исключений .NET, так и ваших собственных, будет утеряна - если использовать стандартный ToString() для логирования. Не поможет даже переопределение метода ToString() в вашем исключении (что, впрочем, в любом случае необходимо сделать, если вы храните информацию в полях, отличных от Message). Взгляните на реализацию ToString() у класса Exception (я привожу сокращенный вариант, а полную версию можно найти в официальных исходных кодах .NET):
public override String ToString()
{
return ToString(true, true);
}
private String ToString(bool needFileLineInfo, bool needMessage)
{
String message = (needMessage ? Message : null);
String s = GetClassName() + ": " + message;
if (_innerException!=null)
{
s = s + " ---> "
+ _innerException.ToString(needFileLineInfo, needMessage)
+ Environment.NewLine
+ " "
+ Environment.GetResourceString("Exception_EndOfInnerExceptionStack");
}
string stackTrace = GetStackTrace(needFileLineInfo);
if (stackTrace != null)
{
s += Environment.NewLine + stackTrace;
}
return s;
}
Итак, проблема в том, что для _innerException вызывается не публичный ToString(), а приватный ToString(bool, bool), естественно недоступный для переопределения. Таким образом, логику обработки вложенных исключений метода ToString() изменить невозможно. Как будем решать проблему?- отказаться от FileNotFoundException и им подобных исключений - невозможно, они могут быть брошены в неподвластном нам коде;
- записывать всю необходимую информацию в Message - невозможно по той же причине;
- пробрасывать FileNotFoundException на самый верх приложения - перечеркнуть всю идею структурной обработки исключений, поломать архитектуру и в перспективе переписать кучу кода.
Похоже, единственный надежный на 100% вариант - гарантировать вызов публичного ToString() для каждого вложенного исключения через метод-расширение:
public static string ToFullString(this Exception exception)
{
if (exception == null) return string.Empty;
return exception
+ Environment.NewLine
+ "-----------------------"
+ Environment.NewLine
+ ToFullString(exception.InnerException);
}
Результат:System.Exception: outer message ---> System.IO.FileNotFoundException: error messageОсторожно! Логируемый текст увеличится многократно, так как каждый вызов ToString() печатает информацию о всех вложенных исключениях, включая все StackTrace'ы. Но полная информация в логах куда важнее их объема во многих сценариях!
--- End of inner exception stack trace ---
-----------------------
System.IO.FileNotFoundException: error message
File name: 'filename'
-----------------------
Полезные ссылки:
Why doesn't System.Exception.ToString call virtual ToString for inner exceptions? - пост на StackOverflow с похожей болью;
О логировании исключений и Exception.ToString - аналогичные проблемы при логировании AggregationException.
Комментариев нет:
Отправить комментарий