Salvedad.Mensaje vs Excepción.toString()


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

¿Es esto cierto, y es seguro seguir adelante 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() contenga caracteres XML no válidos, lo que puede causar problemas?

Author: Jesse, 2010-02-01

7 answers

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

Referencia de objeto no establecida en una instancia de un objeto

El Exception.ToString() method dará una salida mucho más detallada, conteniendo el tipo de excepción, el mensaje (de antes), un seguimiento de pila, y todas estas cosas de nuevo para las excepciones anidadas/internas. Más precisamente, el método devuelve lo siguiente:

ToString devuelve una representación de la excepción actual que está destinada a ser entendida por los humanos. Cuando la excepción contiene datos sensibles a la cultura, la representación de cadena devuelta por toString es necesaria para tener en cuenta la cultura del sistema actual. 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 lanzó la corriente exception, el mensaje, el resultado de llamar a toString en la excepción interna, y el resultado de llamar al entorno.StackTrace. Si alguno de estos miembros es una referencia null (Nada en Visual Basic), su valor no se incluye en la cadena devuelta.

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

 222
Author: Jørn Schou-Rode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-01 12:55:46

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

En términos de propósitos de registro, definitivamente use ToString() en la Excepción, no solo en la propiedad Message, como en la mayoría de los escenarios, se quedará rascándose la cabeza donde específicamente ocurrió esta excepción y cuál era la pila de llamadas. El stacktrace te habría dicho todo eso.

 45
Author: Wim Hollebrandse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-01 13:10:18

Como señala @JornSchouRode, hacer un exception.ToString() le da más información que simplemente usar la propiedad exception.Message. Sin embargo, incluso esto todavía deja fuera mucha información, incluyendo:

  1. La propiedad Data collection se encuentra en todas las excepciones.
  2. Cualquier otra propiedad personalizada añadida 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 buena orden. Está usando C # 6.0, pero debería ser muy fácil para usted convertir a versiones anteriores si es necesario. Véase también esta respuesta relacionada.

public static class ExceptionExtensions
{
    public static string ToDetailedString(this Exception exception)
    {
        if (exception == null)
        {
            throw new ArgumentNullException(nameof(exception));
        }

        return ToDetailedString(exception, ExceptionOptions.Default);
    }

    public static string ToDetailedString(this Exception exception, ExceptionOptions options)
    {
        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; }
}

Consejo superior-Excepciones de registro

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

Consejo superior-Trazas de Pila legibles por humanos

Puedes usar el Ben.Demystifier Paquete NuGet para obtener trazas de pila legibles por humanos para sus excepciones o el paquete serilog-enrichers-demystify NuGet si está utilizando Serilog.

 12
Author: Muhammad Rehan Saeed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-14 09:25:40

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

También, además de la traza apilada, ToString() incluirá información que no obtendrás de otra manera. Por ejemplo, la salida de fusion, si habilitó para incluir mensajes de registro en la excepción "mensaje".

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

 9
Author: Christian.K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-08 09:46:22

Depende de la información que necesite. Para depurar el seguimiento de 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";
    }
 8
Author: Carra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-01 12:59:03

En cuanto al formato XML para log4net, no necesita preocuparse por ex.ToString() para los registros. Simplemente pase el objeto de excepción en sí y log4net hace el resto le dará todos los detalles en su formato XML preconfigurado. Lo único que me encuentro en ocasiones es un nuevo formato de línea, pero ahí es cuando estoy leyendo los archivos raw. De lo contrario, analizar el XML funciona muy bien.

 3
Author: Dillie-O,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-01 15:07:21

Bueno, yo diría que depende de lo que quieras ver en los registros, ¿no? Si estás contento con lo que ex.Mensaje proporciona, utilice eso. De lo contrario, utilice ex.toString () o incluso registrar el seguimiento de la pila.

 1
Author: Thorsten Dittmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-01 12:56:37