Exception.Message와 Exception.ToString ()


207

로깅하는 코드가 있습니다 Exception.Message. 그러나 사용하는 것이 더 낫다는 기사를 읽었습니다 Exception.ToString(). 후자는 오류에 대한보다 중요한 정보를 유지합니다.

이것이 사실 Exception.Message입니까? 계속 진행하고 모든 코드 로깅을 바꾸는 것이 안전 합니까?

log4net에 XML 기반 레이아웃을 사용하고 있습니다. 그것은 가능성이 Exception.ToString()문제가 발생할 수있는 잘못된 XML 문자를 포함 할 수 있습니다?


1
ASP.NET의 오류 로깅을위한 매우 사용하기 쉬운 프레임 워크 인 ELMAH ( code.google.com/p/elmah )도 참조하십시오.
Ashish Gupta

답변:


278

Exception.Message예외와 관련된 메시지 (doh) 만 포함합니다. 예:

객체 참조가 객체의 인스턴스로 설정되지 않았습니다

Exception.ToString()메소드는 예외 유형, 메시지 (이전부터), 스택 추적 및 중첩 / 내부 예외에 대한 이러한 모든 사항을 포함하는 훨씬 더 자세한 출력을 제공합니다. 보다 정확하게는이 메서드는 다음을 반환합니다.

ToString은 사람이 이해하려고하는 현재 예외의 표현을 반환합니다. 예외에 문화권 구분 데이터가 포함 된 경우 현재 시스템 문화권을 고려하려면 ToString에서 반환 한 문자열 표현이 필요합니다. 반환 된 문자열의 형식에 대한 정확한 요구 사항은 없지만 사용자가 인식 한대로 개체의 값을 반영해야합니다.

ToString의 기본 구현은 현재 예외를 발생시킨 클래스 이름, 메시지, 내부 예외에서 ToString을 호출 한 결과 및 Environment.StackTrace를 호출 한 결과를 얻습니다. 이러한 멤버 중 하나가 null 참조 (Visual Basic의 경우 Nothing)이면 해당 값이 반환 된 문자열에 포함되지 않습니다.

오류 메시지가 없거나 빈 문자열 ( "")이면 오류 메시지가 반환되지 않습니다. 내부 예외 이름과 스택 추적은 Null 참조가 아닌 경우에만 반환됩니다 (Visual Basic의 경우 Nothing).


85
+1 로그에서 "객체 참조가 객체의 인스턴스로 설정되지 않음"만 보는 것은 매우 고통 스럽습니다. 당신은 정말로 무력감을 느낍니다. :-)
Ashish Gupta 2016 년

1
마지막 부분에는 Exception.Message와 함께 제공되지 않는 예외가 있습니다. 오류 처리 부분에서 수행하는 기능에 따라 Exception.Message로 인해 문제가 발생할 수 있습니다.
산호 도우

49
본질적으로 ToString ()과 동일한 기능을 수행하는 코드를 작성했음을 아는 것은 매우 고통 스럽습니다.
Preston McCormick

1
@KunalGoel 로그가 prod에서 왔고 입력이 무엇인지 표시하지 않으면 "CLR 예외를 설정하여 디버그"할 수 없습니다.
jpmc26

1
"ToString의 기본 구현"입니다 ( "default"에 강조 표시). 모든 사용자 정의 예외와 함께 해당 관행을 따 랐음을 의미하지는 않습니다. #learnedTheHardWay
granadaCoder

52

이미 말한 것 외에도 예외 객체를 사용 하여 사용자에게 표시 하지 마십시오ToString() . 그냥 Message속성은 충분, 또는 높은 수준의 사용자 정의 메시지해야한다.

로깅의 관점에서 볼 때, 대부분의 시나리오에서와 같이 속성 ToString()뿐만 아니라 예외에 Message대해서도 확실하게 사용 하면이 예외가 발생한 위치와 호출 스택이 무엇인지 머리를 긁을 수 있습니다. 스택 트레이스가 모든 것을 말해 줄 것입니다.


로그에 ToString ()을 사용하는 경우 ToString에 민감한 정보를 포함시키지 마십시오.
Michael Freidgeim

22

WHOLE 예외를 문자열로 변환

전화 Exception.ToString()Exception.Message속성을 사용하는 것보다 더 많은 정보를 제공합니다 . 그러나 이것조차도 여전히 다음을 포함한 많은 정보를 남깁니다.

  1. Data컬렉션 속성은 모든 예외를 발견했다.
  2. 다른 사용자 정의 특성이 예외에 추가되었습니다.

이 추가 정보를 캡처하려는 경우가 있습니다. 아래 코드는 위의 시나리오를 처리합니다. 또한 예외의 속성을 좋은 순서로 작성합니다. C # 7을 사용하고 있지만 필요한 경우 이전 버전으로 쉽게 변환 할 수 있어야합니다. 관련 답변을 참조하십시오 .

public static class ExceptionExtensions
{
    public static string ToDetailedString(this Exception exception) =>
        ToDetailedString(exception, ExceptionOptions.Default);

    public static string ToDetailedString(this Exception exception, ExceptionOptions options)
    {
        if (exception == null)
        {
            throw new ArgumentNullException(nameof(exception));
        } 

        var stringBuilder = new StringBuilder();

        AppendValue(stringBuilder, "Type", exception.GetType().FullName, options);

        foreach (PropertyInfo property in exception
            .GetType()
            .GetProperties()
            .OrderByDescending(x => string.Equals(x.Name, nameof(exception.Message), StringComparison.Ordinal))
            .ThenByDescending(x => string.Equals(x.Name, nameof(exception.Source), StringComparison.Ordinal))
            .ThenBy(x => string.Equals(x.Name, nameof(exception.InnerException), StringComparison.Ordinal))
            .ThenBy(x => string.Equals(x.Name, nameof(AggregateException.InnerExceptions), StringComparison.Ordinal)))
        {
            var value = property.GetValue(exception, null);
            if (value == null && options.OmitNullProperties)
            {
                if (options.OmitNullProperties)
                {
                    continue;
                }
                else
                {
                    value = string.Empty;
                }
            }

            AppendValue(stringBuilder, property.Name, value, options);
        }

        return stringBuilder.ToString().TrimEnd('\r', '\n');
    }

    private static void AppendCollection(
        StringBuilder stringBuilder,
        string propertyName,
        IEnumerable collection,
        ExceptionOptions options)
        {
            stringBuilder.AppendLine($"{options.Indent}{propertyName} =");

            var innerOptions = new ExceptionOptions(options, options.CurrentIndentLevel + 1);

            var i = 0;
            foreach (var item in collection)
            {
                var innerPropertyName = $"[{i}]";

                if (item is Exception)
                {
                    var innerException = (Exception)item;
                    AppendException(
                        stringBuilder,
                        innerPropertyName,
                        innerException,
                        innerOptions);
                }
                else
                {
                    AppendValue(
                        stringBuilder,
                        innerPropertyName,
                        item,
                        innerOptions);
                }

                ++i;
            }
        }

    private static void AppendException(
        StringBuilder stringBuilder,
        string propertyName,
        Exception exception,
        ExceptionOptions options)
    {
        var innerExceptionString = ToDetailedString(
            exception, 
            new ExceptionOptions(options, options.CurrentIndentLevel + 1));

        stringBuilder.AppendLine($"{options.Indent}{propertyName} =");
        stringBuilder.AppendLine(innerExceptionString);
    }

    private static string IndentString(string value, ExceptionOptions options)
    {
        return value.Replace(Environment.NewLine, Environment.NewLine + options.Indent);
    }

    private static void AppendValue(
        StringBuilder stringBuilder,
        string propertyName,
        object value,
        ExceptionOptions options)
    {
        if (value is DictionaryEntry)
        {
            DictionaryEntry dictionaryEntry = (DictionaryEntry)value;
            stringBuilder.AppendLine($"{options.Indent}{propertyName} = {dictionaryEntry.Key} : {dictionaryEntry.Value}");
        }
        else if (value is Exception)
        {
            var innerException = (Exception)value;
            AppendException(
                stringBuilder,
                propertyName,
                innerException,
                options);
        }
        else if (value is IEnumerable && !(value is string))
        {
            var collection = (IEnumerable)value;
            if (collection.GetEnumerator().MoveNext())
            {
                AppendCollection(
                    stringBuilder,
                    propertyName,
                    collection,
                    options);
            }
        }
        else
        {
            stringBuilder.AppendLine($"{options.Indent}{propertyName} = {value}");
        }
    }
}

public struct ExceptionOptions
{
    public static readonly ExceptionOptions Default = new ExceptionOptions()
    {
        CurrentIndentLevel = 0,
        IndentSpaces = 4,
        OmitNullProperties = true
    };

    internal ExceptionOptions(ExceptionOptions options, int currentIndent)
    {
        this.CurrentIndentLevel = currentIndent;
        this.IndentSpaces = options.IndentSpaces;
        this.OmitNullProperties = options.OmitNullProperties;
    }

    internal string Indent { get { return new string(' ', this.IndentSpaces * this.CurrentIndentLevel); } }

    internal int CurrentIndentLevel { get; set; }

    public int IndentSpaces { get; set; }

    public bool OmitNullProperties { get; set; }
}

맨 위 팁-로깅 예외

대부분의 사람들은이 코드를 로깅에 사용합니다. Serilog 와 함께 Serilog 를 사용하는 것을 고려하십시오. 예제 NuGet 패키지는 예외의 모든 속성을 기록하지만 대부분의 경우에 반영하지 않고 더 빠르게 수행합니다. Serilog는 작성 당시의 모든 분노 인 매우 고급 로깅 ​​프레임 워크입니다.

맨 위 팁-사람이 읽을 수있는 스택 추적

Serilog 를 사용하는 경우 Ben.Demystifier NuGet 패키지를 사용 하여 예외에 대한 사람이 읽을 수있는 스택 추적 또는 serilog-enrichers-demystify NuGet 패키지를 얻을 수 있습니다.


9

@Wim이 옳습니다. ToString()기술적 대상이 있다고 가정 Message하고 사용자에게 표시하려면 로그 파일에 사용해야 합니다 . 사용자는 모든 예외 유형과 그 밖의 발생에 대해 사용자에게 적합하지 않다고 주장 할 수 있습니다 (ArgumentExceptions 등을 고려하십시오).

또한 StackTrace 외에도 다른 방법으로는 ToString()얻을 수없는 정보가 포함됩니다. 예를 들어, 예외 "메시지"에 로그 메시지를 포함 할 수있는 경우 융합 출력 .

일부 예외 유형에는 ToString()메시지에 포함되지 않은 추가 정보 (예 : 사용자 지정 속성)가 포함되어 있습니다.


8

필요한 정보에 따라 다릅니다. 스택 추적 및 내부 예외를 디버깅하는 데 유용합니다.

    string message =
        "Exception type " + ex.GetType() + Environment.NewLine +
        "Exception message: " + ex.Message + Environment.NewLine +
        "Stack trace: " + ex.StackTrace + Environment.NewLine;
    if (ex.InnerException != null)
    {
        message += "---BEGIN InnerException--- " + Environment.NewLine +
                   "Exception type " + ex.InnerException.GetType() + Environment.NewLine +
                   "Exception message: " + ex.InnerException.Message + Environment.NewLine +
                   "Stack trace: " + ex.InnerException.StackTrace + Environment.NewLine +
                   "---END Inner Exception";
    }

12
이것은 Exception.ToString()당신에게 무엇 을 줄 것입니까?
Jørn Schou-Rode

5
@Matt : StringBuilder이 시나리오에서 인스턴스를 구성하는 것은 두 개의 새로운 문자열 할당보다 비용이 많이들 수 있으며 여기서는 더 효율적일 것입니다. 우리가 반복을 다루는 것과는 다릅니다. 코스 말.
Wim Hollebrandse

2
여기서 문제는 가장 바깥 쪽 예외의 "InnerException"만 얻는다는 것입니다. IOW, InnerException 자체에 InnerException이 설정되어 있으면 덤프하지 않습니다 (처음에 원한다고 가정). 나는 정말로 ToString ()을 고수했다.
Christian.K

6
ex.ToString을 사용하십시오. 그것은 당신에게 모든 세부 사항을 가져옵니다.
John Saunders

3
@Christian : 컴파일러는 +가 여러 개인 제정신입니다. 예를 들어 "+ 연산자는 사용하기 쉽고 직관적 인 코드를 만듭니다. 한 명령문에서 여러 + 연산자를 사용하더라도 문자열 내용은 한 번만 복사됩니다." 에서 msdn.microsoft.com/en-us/library/ms228504.aspx
데이비드 Eison

3

log4net의 XML 형식과 관련하여 로그의 ex.ToString ()에 대해 걱정할 필요가 없습니다. 예외 객체 자체를 전달하면 log4net은 미리 구성된 XML 형식으로 모든 세부 정보를 제공합니다. 가끔 내가 만날 수있는 것은 줄 바꿈 형식이지만 파일을 원시로 읽을 때입니다. 그렇지 않으면 XML을 구문 분석하는 것이 좋습니다.


0

글쎄, 나는 그것이 당신이 로그에서보고 싶은 것에 달려 있다고 말하지 않습니까? ex.Message가 제공하는 것에 만족한다면 그것을 사용하십시오. 그렇지 않으면 ex.toString ()을 사용하거나 스택 추적을 기록하십시오.


5
ex.ToString은 스택 트레이스를 포함합니다
John Saunders
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.