Exception.Message vs Exception.ToString ()

207

Tengo un código que está registrando Exception.Message. Sin embargo, leí un artículo que dice que es mejor usarlo Exception.ToString(). Con este último, retiene información más crucial sobre el error.

¿Es esto cierto y es seguro continuar y reemplazar todo el registro de código Exception.Message?

También estoy usando un diseño basado en XML para log4net . ¿Es posible que Exception.ToString()pueda contener caracteres XML no válidos, lo que puede causar problemas?

JL
fuente
1
También debe consultar ELMAH ( code.google.com/p/elmah ): un marco muy fácil de usar para el registro de errores para ASP.NET.
Ashish Gupta

Respuestas:

278

Exception.Messagecontiene solo el mensaje (doh) asociado con la excepción. Ejemplo:

Referencia a objeto no establecida como instancia de un objeto

El Exception.ToString()método dará un resultado mucho más detallado, que contiene el tipo de excepción, el mensaje (de antes), un seguimiento de la pila y todas estas cosas nuevamente para excepciones anidadas / internas. Más precisamente, el método devuelve lo siguiente:

ToString devuelve una representación de la excepción actual que debe ser entendida por los humanos. Cuando la excepción contiene datos sensibles a la cultura, la representación de cadena devuelta por ToString debe tener en cuenta la cultura actual del sistema. Aunque no hay requisitos exactos para el formato de la cadena devuelta, debe intentar reflejar el valor del objeto tal como lo percibe el usuario.

La implementación predeterminada de ToString obtiene el nombre de la clase que arrojó la excepción actual, el mensaje, el resultado de llamar a ToString en la excepción interna y el resultado de llamar a Environment.StackTrace. Si alguno de estos miembros es una referencia nula (Nothing en Visual Basic), su valor no se incluye en la cadena devuelta.

Si no hay un mensaje de error o si se trata de una cadena vacía (""), no se devuelve ningún mensaje de error. El nombre de la excepción interna y el seguimiento de la pila se devuelven solo si no son una referencia nula (Nothing en Visual Basic).

Jørn Schou-Rode
fuente
86
+1 Es muy doloroso ver SOLO que "Referencia de objeto no establecida en una instancia de un objeto" en los registros. Te sientes realmente impotente. :-)
Ashish Gupta
1
Para la última parte, hay excepciones que no vienen con Exception.Message. En función de lo que hace en la parte de manejo de errores, puede llegar a tener problemas debido a Exception.Message.
Coral Doe
50
Es muy doloroso ver que escribí un código que esencialmente hace exactamente lo mismo que ToString ().
Preston McCormick
1
@KunalGoel Si el registro proviene de prod y no tiene indicación de cuál fue la entrada, entonces no, no puede simplemente "depurar mediante la activación de la excepción CLR".
jpmc26
1
Tenga en cuenta que es la "implementación predeterminada de ToString" ... (énfasis en "predeterminado") ... no significa que todos hayan seguido esa práctica con ninguna excepción personalizada. #learnedTheHardWay
granadaCoder
52

Además de lo que ya se ha dicho, no lo use ToString()en el objeto de excepción para mostrarlo al usuario. Solo la Messagepropiedad debería ser suficiente, o un mensaje personalizado de nivel superior.

En términos de fines de registro, definitivamente use ToString()en la Excepción, no solo en la Messagepropiedad, ya que en la mayoría de los escenarios, se quedará rascándose la cabeza donde ocurrió específicamente esta excepción y cuál fue la pila de llamadas. El stacktrace te hubiera dicho todo eso.

Wim Hollebrandse
fuente
Si está utilizando ToString () en los registros, asegúrese de no incluir información confidencial en ToString
Michael Freidgeim
22

Convertir toda la excepción a una cadena

Llamar Exception.ToString()le brinda más información que simplemente usar la Exception.Messagepropiedad. Sin embargo, incluso esto todavía deja mucha información, que incluye:

  1. los Data propiedad de colección se encuentra en todas las excepciones.
  2. Cualquier otra propiedad personalizada agregada a la excepción.

Hay momentos en los que desea capturar esta información adicional. El siguiente código maneja los escenarios anteriores. También escribe las propiedades de las excepciones en un orden agradable. Está utilizando C # 7, pero debería ser muy fácil para usted convertir a versiones anteriores si es necesario. Ver también esta respuesta relacionada.

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; }
}

Sugerencia: excepciones de registro

La mayoría de las personas usarán este código para iniciar sesión. Considere usar Serilog con mi paquete Serilog.Exceptions NuGet que también registra todas las propiedades de una excepción pero lo hace más rápido y sin reflejos en la mayoría de los casos. Serilog es un marco de registro muy avanzado que está de moda en el momento de la escritura.

Sugerencia: trazas de pila legible para humanos

Puede usar el paquete NuGet de Ben.Demystifier para obtener rastros de pila legibles por humanos para sus excepciones o el paquete NuGet serilog-enrichers- desmystify si está usando Serilog.

Muhammad Rehan Saeed
fuente
9

Yo diría que @Wim tiene razón. Debe usarlo ToString()para los archivos de registro, suponiendo una audiencia técnica, y Message, si es que lo hace, para mostrarlo al usuario. Se podría argumentar que incluso eso no es adecuado para un usuario, para cada tipo de excepción y ocurrencia (piense en ArgumentExceptions, etc.).

Además, además de StackTrace, ToString()incluirá información que no obtendrá de otra manera. Por ejemplo, la salida de fusión, si está habilitada para incluir mensajes de registro en "mensajes" de excepción.

Algunos tipos de excepciones incluso incluyen información adicional (por ejemplo, de propiedades personalizadas) en ToString(), pero no en el Mensaje.

Christian.K
fuente
8

Depende de la información que necesite. Para depurar, el seguimiento de la pila y la excepción interna son útiles:

    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";
    }
Carra
fuente
12
Esto es más o menos lo que Exception.ToString()te dará, ¿verdad?
Jørn Schou-Rode
55
@Matt: construir una instancia de StringBuilderen este escenario puede ser más costoso que dos nuevas asignaciones de cadenas, es muy discutible que sería más eficiente aquí. No es como si estuviéramos lidiando con iteraciones. Caballos de carreras.
Wim Hollebrandse
2
El problema aquí es que solo obtendrá la "InnerException" de la excepción más externa. IOW, si InnerException en sí tiene un conjunto InnerException establecido, no lo volcará (suponiendo que lo desee en primer lugar). Realmente me quedaría con ToString ().
Christian.K
66
Solo usa ex.ToString. Te da todos los detalles.
John Saunders
3
@Christian: el compilador es cuerdo con múltiples + s. Consulte, por ejemplo, "El operador + es fácil de usar y crea un código intuitivo. Incluso si usa varios operadores + en una sola declaración, el contenido de la cadena se copia solo una vez". de msdn.microsoft.com/en-us/library/ms228504.aspx
David Eison el
3

En términos del formato XML para log4net, no necesita preocuparse por ej. ToString () para los registros. Simplemente pase el objeto de excepción en sí mismo y log4net hace el resto para darle todos los detalles en su formato XML preconfigurado. Lo único con lo que me encuentro en ocasiones es el nuevo formato de línea, pero es cuando estoy leyendo los archivos sin formato. De lo contrario, analizar el XML funciona muy bien.

Dillie-O
fuente
0

Bueno, yo diría que depende de lo que quieras ver en los registros, ¿no? Si está satisfecho con lo que proporciona el mensaje, utilícelo. De lo contrario, use ex.toString () o incluso registre el seguimiento de la pila.

Thorsten Dittmar
fuente
66
ex.ToString incluye el seguimiento de la pila
John Saunders