Exception.MessageとException.ToString()


207

ロギングするコードがありException.Messageます。しかし、私はを使用する方が良いと述べている記事を読みましたException.ToString()。後者では、エラーに関するより重要な情報を保持できます。

これは本当Exception.Messageですか?そして先に進んですべてのコードロギングを置き換えることは安全ですか?

また、log4netにはXMLベースのレイアウトを使用しています。Exception.ToString()問題の原因となる無効なXML文字が含まれている可能性はありますか?


1
ELMAH(code.google.com/p/elmah-ASP.NETのエラーログ用の非常に使いやすいフレームワークも確認する必要があります。
Ashish Gupta、

回答:


278

Exception.Message例外に関連付けられたメッセージ(doh)のみが含まれます。例:

オブジェクト参照がオブジェクトインスタンスに設定されていません

このException.ToString()メソッドは、例外タイプ、メッセージ(以前からの)、スタックトレース、およびネストされた例外や内部例外についてのこれらすべてのすべてを含む、より詳細な出力を提供します。より正確には、このメソッドは以下を返します。

ToStringは、人間が理解することを目的とした現在の例外の表現を返します。例外にカルチャに依存するデータが含まれている場合、ToStringによって返される文字列表現は、現在のシステムカルチャを考慮する必要があります。返される文字列の形式に厳密な要件はありませんが、ユーザーが認識するオブジェクトの値を反映する必要があります。

ToStringのデフォルト実装は、現在の例外をスローしたクラスの名前、メッセージ、内部例外でToStringを呼び出した結果、およびEnvironment.StackTraceを呼び出した結果を取得します。これらのメンバーのいずれかがnull参照(Visual BasicではNothing)の場合、その値は返される文字列に含まれません。

エラーメッセージがない場合、または空の文字列( "")の場合、エラーメッセージは返されません。内部例外の名前とスタックトレースは、null参照(Visual BasicではNothing)でない場合にのみ返されます。


86
+1ログで「オブジェクト参照がオブジェクトのインスタンスに設定されていない」ことだけを確認するのは非常に痛いです。あなたは本当に無力だと感じます。:-)
Ashish Gupta

1
最後の部分には、Exception.Messageに付属しない例外があります。エラー処理部分で行うことの関数として、Exception.Messageが原因で問題が発生する可能性があります。
Coral Doe

50
ToString()が行うのとまったく同じことを本質的に行うコードを私が書いたことを確認するのはとても痛いです。
プレストンマコーミック

1
@KunalGoelログがprodからのものであり、入力が何であるかがわからない場合は、いいえ、「CLR例外をオンにしてデバッグする」ことはできません。
jpmc26

1
これは「ToStringのデフォルト実装」...(「デフォルト」に重点を置いている)であることに注意してください。これは、すべてのカスタム例外を使用してすべての人がその慣習に従っていることを意味するものではありません。#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.Exceptions NuGetパッケージでSerilogを使用することを検討してください。これは、例外のすべてのプロパティもログに記録しますが、ほとんどの場合、リフレクションなしで高速に実行します。Serilogは非常に高度なロギングフレームワークであり、執筆時点では大流行しています。

ヒント-人間が読めるスタックトレース

Ben.Demystifier NuGetパッケージを使用して、人間が読める例外のスタックトレースを取得できます。Serilogを使用している場合は、serilog-enrichers-demystify NuGetパッケージを使用できます。


9

@Wimは正しいと思います。ToString()ログファイルには-技術的な読者を想定して- 使用Messageし、ユーザーに表示する場合はを使用してください。それでも、すべての例外タイプとそこに発生する例外(ArgumentExceptionsなどを考える)については、ユーザーにとって適切ではないと主張することができます。

また、StackTraceに加えて、他の方法では取得ToString()できない情報が含まれます。たとえば、例外の「メッセージ」にログメッセージを含めることが有効になっている場合、fusionの出力。

一部の例外タイプには、追加の情報(たとえば、カスタムプロパティから)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()あなたに与えるものですよね?
ジョーンSchouの-Rodeの

5
@マット:StringBuilderこのシナリオでのインスタンスの構築は、2つの新しい文字列の割り当てよりも費用がかかる可能性があります。議論の余地があり、ここではより効率的です。繰り返しを扱っているわけではありません。コース用の馬。
Wim Hollebrandse、2010

2
ここでの問題は、最も外側の例外の「InnerException」のみを取得することです。IOW、InnerException自体にInnerExceptionが設定されている場合は、それをダンプしません(最初に必要な場合)。私は本当にToString()を使い続けます。
Christian.K

6
ex.ToStringを使用するだけです。それはあなたにすべての詳細を取得します。
John Saunders

3
@Christian:コンパイラーは複数の+で正気です。たとえば、「+演算子は使いやすく、直感的なコードになります。1つのステートメントで複数の+演算子を使用しても、文字列の内容は1回だけコピーされます。」msdn.microsoft.com/en-us/library/ms228504.aspx
David Eison 2012

3

log4netのXML形式に関しては、ログのex.ToString()について心配する必要はありません。単に例外オブジェクト自体を渡すだけで、log4netは残りを事前構成されたXML形式ですべての詳細を提供します。たまに出くわすのは改行フォーマットだけですが、それはファイルを生で読み取るときです。それ以外の場合、XMLの構文解析はうまく機能します。


0

ええと、ログで何を表示したいかによって異なりますね。ex.Messageの機能に満足している場合は、それを使用してください。それ以外の場合は、ex.toString()を使用するか、スタックトレースをログに記録します。


6
ex.ToStringには、スタックトレースが含まれています
John Saunders
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.