Exception.Message vs Exception.ToString ()


207

Exception.Messageฉันมีรหัสที่มีการเข้าสู่ระบบ Exception.ToString()แต่ผมอ่านบทความที่ระบุว่ามันจะดีกว่ากับการใช้ ด้วยข้อมูลหลังนี้คุณจะเก็บข้อมูลสำคัญเกี่ยวกับข้อผิดพลาดได้มากขึ้น

สิ่งนี้เป็นจริงและปลอดภัยหรือไม่ที่จะไปข้างหน้าและแทนที่การบันทึกรหัสทั้งหมดException.Message?

ฉันใช้เลย์เอาต์ XML สำหรับlog4netด้วย เป็นไปได้หรือไม่ที่Exception.ToString()อาจมีอักขระ XML ไม่ถูกต้องซึ่งอาจทำให้เกิดปัญหา


1
คุณควรดู ELMAH ( code.google.com/p/elmah ) - กรอบงานที่ใช้งานง่ายมากสำหรับการบันทึกข้อผิดพลาดสำหรับ ASP.NET
Ashish Gupta

คำตอบ:


278

Exception.Messageมีเฉพาะข้อความ (DOH) ที่เกี่ยวข้องกับข้อยกเว้น ตัวอย่าง:

การอ้างอิงวัตถุไม่ได้ถูกตั้งค่าเป็นอินสแตนซ์ของวัตถุ

Exception.ToString()วิธีการจะให้มากขึ้น verbose เอาท์พุทที่มีชนิดยกเว้นข้อความ (จากก่อนหน้านี้), กองติดตามและทุกสิ่งเหล่านี้อีกครั้งสำหรับข้อยกเว้นที่ซ้อนกัน / ภายใน แม่นยำยิ่งขึ้นวิธีคืนค่าต่อไปนี้:

ToString ส่งคืนการแสดงข้อยกเว้นปัจจุบันที่มนุษย์ต้องการให้เข้าใจ ในกรณีที่ข้อยกเว้นมีข้อมูลที่มีความอ่อนไหวทางวัฒนธรรมจำเป็นต้องใช้การแทนค่าสตริงที่ส่งคืนโดย ToString เพื่อพิจารณาถึงวัฒนธรรมของระบบปัจจุบัน แม้ว่าจะไม่มีข้อกำหนดที่แน่นอนสำหรับรูปแบบของสตริงที่ส่งคืน แต่ควรพยายามสะท้อนค่าของวัตถุตามการรับรู้ของผู้ใช้

การใช้งานเริ่มต้นของ ToString รับชื่อของคลาสที่โยนข้อยกเว้นปัจจุบันข้อความผลลัพธ์ของการเรียก ToString บนข้อยกเว้นภายในและผลลัพธ์ของการเรียก Environment.StackTrace ถ้าสมาชิกใด ๆ เหล่านี้เป็นการอ้างอิงที่ไม่มีค่า (ไม่มีใน Visual Basic) ค่าของมันจะไม่รวมอยู่ในสตริงที่ส่งคืน

หากไม่มีข้อความแสดงข้อผิดพลาดหรือเป็นสตริงว่าง ("") แสดงว่าไม่มีข้อความแสดงข้อผิดพลาดปรากฏขึ้น ชื่อของข้อยกเว้นภายในและการติดตามสแต็กจะถูกส่งกลับเฉพาะในกรณีที่ไม่ใช่การอ้างอิงแบบ null (ไม่มีอะไรใน Visual Basic)


85
+1 มันเจ็บปวดมากที่เห็นเพียงอย่างเดียวว่า "การอ้างอิงวัตถุไม่ได้ถูกตั้งค่าเป็นอินสแตนซ์ของวัตถุ" ในบันทึก คุณรู้สึกหมดหนทางจริงๆ :-)
Ashish Gupta

1
สำหรับส่วนสุดท้ายมีข้อยกเว้นที่ไม่ได้มาพร้อมกับข้อยกเว้นข้อความ ในฟังก์ชั่นของสิ่งที่คุณทำในส่วนการจัดการข้อผิดพลาดคุณจะได้รับปัญหาเนื่องจากการ Exception.Message
Coral Doe

49
มันเจ็บปวดมากที่เห็นว่าฉันเขียนโค้ดที่ทำสิ่งเดียวกันกับ ToString ()
Preston McCormick

1
@KunalGoel หากบันทึกนั้นมาจากการผลิตและคุณไม่มีข้อบ่งชี้ว่าข้อมูลเข้านั้นเป็นอย่างไรไม่คุณไม่สามารถ "แก้ปัญหาโดยการเปิดข้อยกเว้น CLR"
jpmc26

1
หมายเหตุมันเป็น "การเริ่มต้นใช้งานของ ToString" ... (เน้นที่ "default") .. ไม่ได้หมายความว่าทุกคนปฏิบัติตามการฝึกด้วยข้อยกเว้นที่กำหนดเองใด ๆ #learnedTheHardWay
granadaCoder

52

นอกเหนือจากสิ่งที่กล่าวไปแล้วอย่าใช้ToString()กับวัตถุข้อยกเว้นสำหรับการแสดงต่อผู้ใช้ เพียงแค่Messageคุณสมบัติควรเพียงพอหรือข้อความที่กำหนดเองในระดับที่สูงขึ้น

ในแง่ของวัตถุประสงค์ในการเข้าสู่ระบบให้ใช้ToString()กับข้อยกเว้นไม่ใช่เฉพาะMessageคุณสมบัติเช่นเดียวกับในสถานการณ์ส่วนใหญ่คุณจะถูกเกาหัวของคุณซึ่งมีข้อยกเว้นนี้เกิดขึ้นเป็นพิเศษและสแต็กการโทรคืออะไร กองซ้อนจะบอกคุณทั้งหมดว่า


หากคุณใช้ ToString () ในบันทึกตรวจสอบให้แน่ใจว่าไม่มีข้อมูลที่ละเอียดอ่อนใน ToString
Michael Freidgeim

22

การแปลงข้อยกเว้นทั้งหมดเป็นสตริง

การโทร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 เป็นกรอบการบันทึกขั้นสูงมากซึ่งเป็นความโกรธในเวลาที่เขียน

เคล็ดลับยอดนิยม - ร่องรอยสแต็กที่มนุษย์อ่านได้

คุณสามารถใช้Ben.Demystifierแพคเกจ NuGet ที่จะได้รับของมนุษย์ร่องรอยสแต็คที่สามารถอ่านได้สำหรับข้อยกเว้นหรือคุณserilog-enrichers-demystifyแพคเกจ NuGet ถ้าคุณกำลังใช้ Serilog


9

ฉันว่า @Wim ถูกต้อง คุณควรใช้ToString()สำหรับ logfiles - สมมติว่าผู้ชมด้านเทคนิค - และ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
@ แมท: การสร้างอินสแตนซ์ของStringBuilderในสถานการณ์นี้อาจมีราคาแพงกว่าการจัดสรรสตริงใหม่สองครั้งมันเป็นที่ถกเถียงกันอย่างมากว่ามันจะมีประสิทธิภาพมากขึ้นที่นี่ ไม่ใช่ว่าเรากำลังทำซ้ำอยู่ ม้าสำหรับหลักสูตร
Wim Hollebrandse

2
ปัญหาที่นี่คือคุณจะได้รับ "InnerException" ของข้อยกเว้นภายนอกเท่านั้น IOW ถ้า InnerException มีชุด InnerException อยู่คุณจะไม่ทิ้งมัน (สมมติว่าคุณต้องการตั้งแต่แรก) ฉันจะติดกับ ToString ()
Christian.K

6
เพียงใช้ ex.ToString มันทำให้คุณได้รับรายละเอียดทั้งหมด
John Saunders

3
@Christian: คอมไพเลอร์มีสติหลาย + s ดูตัวอย่าง "ตัวดำเนินการ + ใช้งานง่ายและสร้างรหัสที่ใช้งานง่ายแม้ว่าคุณจะใช้ตัวดำเนินการ + หลายตัวในคำสั่งเดียวเนื้อหาสตริงจะถูกคัดลอกเพียงครั้งเดียว" จากmsdn.microsoft.com/en-us/library/ms228504.aspx
David Eison

3

ในแง่ของรูปแบบ XML สำหรับ log4net คุณไม่จำเป็นต้องกังวลเกี่ยวกับ 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.