Exception.Message vs Exception.ToString ()

207

Eu tenho um código que está registrando Exception.Message . No entanto, li um artigo que afirma que é melhor usar Exception.ToString(). Com o último, você retém informações mais cruciais sobre o erro.

Isso é verdade e é seguro ir em frente e substituir todo o registro de código Exception.Message ?

Também estou usando um layout baseado em XML para log4net . É possível que Exception.ToString()possam conter caracteres XML inválidos, o que pode causar problemas?

JL.
fonte
1
Você também deve consultar o ELMAH ( code.google.com/p/elmah ) - uma estrutura muito fácil de usar para o Log de erros do ASP.NET.
Ashish Gupta

Respostas:

278

Exception.Messagecontém apenas a mensagem (doh) associada à exceção. Exemplo:

Referência de objeto não definida para uma instância de um objeto

O Exception.ToString()método fornecerá uma saída muito mais detalhada, contendo o tipo de exceção, a mensagem (de antes), um rastreamento de pilha e todas essas coisas novamente para exceções aninhadas / internas. Mais precisamente, o método retorna o seguinte:

ToString retorna uma representação da exceção atual que deve ser entendida por humanos. Onde a exceção contém dados sensíveis à cultura, a representação de string retornada pelo ToString é necessária para levar em consideração a cultura atual do sistema. Embora não haja requisitos exatos para o formato da sequência retornada, ela deve tentar refletir o valor do objeto conforme percebido pelo usuário.

A implementação padrão do ToString obtém o nome da classe que lançou a exceção atual, a mensagem, o resultado da chamada do ToString na exceção interna e o resultado da chamada do Environment.StackTrace. Se algum desses membros for uma referência nula (Nada no Visual Basic), seu valor não será incluído na cadeia de caracteres retornada.

Se não houver mensagem de erro ou se for uma sequência vazia (""), nenhuma mensagem de erro será retornada. O nome da exceção interna e o rastreamento da pilha serão retornados apenas se não forem uma referência nula (Nada no Visual Basic).

Jørn Schou-Rode
fonte
86
+1 É muito doloroso ver SOMENTE que "Referência de objeto não definida como uma instância de um objeto" nos logs. Você se sente realmente desamparado. :-)
Ashish Gupta
1
Para a última parte, existem exceções que não vêm com Exception.Message. Em função do que você faz na parte de tratamento de erros, você pode ter problemas devido ao Exception.Message.
Coral Doe
50
É muito doloroso ver que eu escrevi um código que essencialmente faz exatamente a mesma coisa que ToString ().
Preston McCormick
1
@KunalGoel Se o log é proveniente de prod e você não tem indicação de qual foi a entrada, não, você não pode simplesmente "depurar ativando a exceção CLR".
jpmc26
1
Observe que é a "implementação padrão do ToString" ... (ênfase em "padrão"). Isso não significa que todos tenham seguido essa prática com exceções personalizadas. #earnedTheHardWay
granadaCoder
52

Além do que já foi dito, não use ToString()no objeto de exceção para exibir ao usuário. Apenas a Messagepropriedade deve ser suficiente ou uma mensagem personalizada de nível superior.

Em termos de fins de registro, definitivamente use ToString()na Exceção, não apenas na Messagepropriedade, como na maioria dos cenários, você ficará coçando a cabeça onde especificamente essa exceção ocorreu e qual era a pilha de chamadas. O stacktrace teria lhe contado tudo isso.

Wim Hollebrandse
fonte
Se você estiver usando ToString () em toras, garantir não incluem informações confidenciais em ToString
Michael Freidgeim
22

Convertendo a exceção inteira em uma seqüência de caracteres

A chamada Exception.ToString()fornece mais informações do que apenas o uso da Exception.Messagepropriedade. No entanto, mesmo isso ainda deixa de fora muita informação, incluindo:

  1. A Datapropriedade de coleção encontrada em todas as exceções.
  2. Quaisquer outras propriedades customizadas adicionadas à exceção.

Há momentos em que você deseja capturar essas informações extras. O código abaixo lida com os cenários acima. Ele também grava as propriedades das exceções em uma boa ordem. Está usando o C # 7, mas deve ser muito fácil converter para versões mais antigas, se necessário. Veja também esta resposta 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; }
}

Dica superior - Exceções de log

A maioria das pessoas estará usando esse código para fazer logon. Considere usar o Serilog com meu pacote NuGet Serilog.Exceptions, que também registra todas as propriedades de uma exceção, mas o faz mais rápido e sem reflexo na maioria dos casos. O Serilog é uma estrutura de registro muito avançada, que é toda a raiva no momento da escrita.

Dica superior - Rastreamentos de pilha legíveis por humanos

Você pode usar o pacote Ben.Demystifier NuGet para obter rastreamentos de pilha legíveis por humanos para suas exceções ou o pacote NuGet serilog-richers- desmystify se você estiver usando o Serilog.

Muhammad Rehan Saeed
fonte
9

Eu diria que @Wim está certo. Você deve usar ToString()para arquivos de log - assumindo uma audiência técnica - e Message, se houver, para exibir ao usuário. Alguém poderia argumentar que mesmo isso não é adequado para um usuário, para todo tipo de exceção e ocorrência lá fora (pense em ArgumentExceptions, etc.).

Além disso, além do StackTrace, ToString()incluirá informações que você não obterá de outra forma. Por exemplo, a saída da fusão, se ativada para incluir mensagens de log na exceção "mensagens".

Alguns tipos de exceção ainda incluem informações adicionais (por exemplo, de propriedades customizadas) em ToString(), mas não na Mensagem.

Christian.K
fonte
8

Depende da informação que você precisa. Para depurar o rastreamento da pilha e a exceção interna são úteis:

    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
fonte
12
Isso é mais ou menos o que Exception.ToString()vai lhe dar, certo?
Jørn Schou-Rode
5
@ Matt: Construir uma instância StringBuildernesse cenário pode muito mais custar do que duas novas alocações de strings, é altamente discutível que seria mais eficiente aqui. Não é como se estivéssemos lidando com iterações. Cavalos para cursos.
Wim Hollebrandse
2
O problema aqui é que você só obterá a "InnerException" da exceção mais externa. IOW, se o InnerException em si tiver um InnerException definido, você não fará o dump (supondo que deseje em primeiro lugar). Eu realmente ficaria com ToString ().
Christian.K
6
Basta usar ex.ToString. Você recebe todos os detalhes.
John Saunders
3
@ Christian: O compilador é sensato com vários + s. Veja, por exemplo, "O operador + é fácil de usar e cria código intuitivo. Mesmo se você usar vários operadores + em uma instrução, o conteúdo da string será copiado apenas uma vez." de msdn.microsoft.com/en-us/library/ms228504.aspx
David Eison
3

Em termos do formato XML para log4net, você não precisa se preocupar com ex.ToString () para os logs. Simplesmente passe o objeto de exceção e o log4net fará o resto com todos os detalhes em seu formato XML pré-configurado. A única coisa que encontro ocasionalmente é a formatação de novas linhas, mas é quando estou lendo os arquivos em bruto. Caso contrário, a análise do XML funciona muito bem.

Dillie-O
fonte
0

Bem, eu diria que depende do que você deseja ver nos logs, não é? Se você estiver satisfeito com o que o ex.Message fornece, use isso. Caso contrário, use ex.toString () ou até registre o rastreamento de pilha.

Thorsten Dittmar
fonte
6
ex.ToString inclui o rastreamento de pilha
John Saunders