Уникальная идентификация исключений .NET - PullRequest
1 голос
/ 30 марта 2011

Я хочу улучшить ведение журнала приложений .NET путем группировки исключений. В идеале мы бы группировали по тому, где они встречались в коде, и по какому типу исключений они были. Таким образом, по сути, мы должны получить список вхождений каждого отдельного исключения (с разными датами, серверными переменными и т. Д.).

Например, если у меня на странице использования регистра есть метод, который 5 раз выдавал исключение нулевой ссылки, я бы перечислил это исключение только один раз со счетом 5 вхождений. Другое исключение нулевой ссылки, создаваемое на другой странице, будет указано отдельно.

Можно ли использовать трассировку стека для надежной идентификации исключений или есть что-то еще, что я пропустил?

Ответы [ 4 ]

4 голосов
/ 30 марта 2011

Если исключение поймано и затем переброшено, то трассировка его стека будет выглядеть так, как если бы исключение произошло в точке переброса, а не в исходном месте. Надеюсь, что в вашем коде не так много всего такого. Если нет, то одной трассировки стека должно быть достаточно, чтобы различить места исключений.

Другая проблема заключается в том, будут ли трассировки стека идентичными, чтобы они не казались двумя разными исключениями, когда они действительно одинаковы. Это также не должно быть проблемой, так как трассировки стека, по моему опыту, идентичны. (Результаты ToString () для объекта исключения, как правило, не будут идентичны.)

ОБНОВЛЕНО

Были некоторые дискуссии о том, каковы именно обстоятельства, что Exception.StackTrace будет поврежден. Основное правило таково, что в любое время

throw *expression*; 

выполняется, тогда для объекта Exception, который идентифицирует *expression*, будет установлено свойство StackTrace. Если *expression* опущено

throw;

тогда StackTrace не будет задействован.

Мне не удалось найти терминологию для этих двух форм, поэтому я буду называть их «явными» и «неявными» соответственно. Обратите внимание, что не имеет значения, является ли объект исключения, к которому разрешается *expression*, new или уже существующим объектом.

Вот программа, иллюстрирующая это поведение:

using System;
namespace FunWithExceptions
{
    class Program
    {
        static void Main(string[] args)
        {
            try { Replace(); }
            catch (InvalidOperationException ex) { DisplayResult("Replace resulted in", ex); }

            try { RethrowExplicit(); }
            catch (InvalidOperationException ex) { DisplayResult("RethrowExplicit resulted in", ex); }

            try { RethrowImplicit(); }
            catch (InvalidOperationException ex) { DisplayResult("RethrowImplicit resulted in", ex); }

            InvalidOperationException myException = new InvalidOperationException();
            DisplayResult("myException starts with", myException);
            try { throw myException; }
            catch (InvalidOperationException) { }
            DisplayResult("myException changes to", myException);
            Console.ReadLine();
        }

        static void ThrowAnException()
        { throw new InvalidOperationException("You messed up!"); }

        static void Replace()
        {
            try { ThrowAnException(); }
            catch (InvalidOperationException ex)
            {
                DisplayResult("Replace caught", ex);
                throw new InvalidOperationException("Another mistake.");
            }
        }

        static void RethrowExplicit()
        {
            try { ThrowAnException(); }
            catch (InvalidOperationException ex)
            {
                DisplayResult("RethrowExplicit caught", ex);
                throw ex;
            }
        }

        static void RethrowImplicit()
        {
            try { ThrowAnException(); }
            catch (InvalidOperationException ex)
            {
                DisplayResult("RethrowImplicit caught", ex);
                throw;
            }
        }

        static void DisplayResult(string context, Exception ex)
        {
            Console.WriteLine("{0} exception thrown at {1}", context, FirstMethodName(ex.StackTrace));
        }

        private const string methodNamePrefix = "   at FunWithExceptions.Program.";

        private static object FirstMethodName(string stackTrace)
        {
            stackTrace = stackTrace ?? string.Empty;
            if (stackTrace.StartsWith(methodNamePrefix))
                stackTrace = stackTrace.Substring(methodNamePrefix.Length);
            int methodNameEndIndex = stackTrace.IndexOf(')');
            if (methodNameEndIndex != -1)
                stackTrace = stackTrace.Substring(0, methodNameEndIndex + 1);
            if (stackTrace.Length > 0)
                return stackTrace;
            else
                return "--empty--";
        }
    }
}

Эта программа приводит к следующему выводу:

Заменить пойманное исключение, выданное ThrowAnException ()

Замена привела к исключению, выданному в Replace ()

RethrowExplicit перехватил исключение, выданное в ThrowAnException ()

RethrowExplicit привел к исключению, брошенному в RethrowExplicit ()

RethrowImplicit перехватил исключение, выданное в ThrowAnException ()

RethrowImplicit привел к исключению, выданному в ThrowAnException ()

myException начинается с исключения, выданного в --empty -

myException изменяется на исключение, выдаваемое в Main (String [] args)

Шестая строка - интересная.

Теперь я полностью забил эту точку до смерти. : -)

1 голос
/ 30 марта 2011

Хорошо, так что вы в основном хотите надежно идентифицировать исключение ПЛЮС местоположение исключения. Трассировка стека кажется мне хорошей. Но вы также можете основать свою идентификацию местоположения, используя имя метода

System.Reflection.MethodBase.GetCurrentMethod()

Вы можете получить все типы исключений из базового исключения, которое вызовет GetCurrentMethod () в конструкторе и предоставит имя метода через свойство только для чтения, чтобы вы знали, где была точка, где было создано исключение в любом месте, которое вы хотите перехватить исключение (я).

Пример:

public class BaseException : ApplicationException {
    public BaseException() {
        _originalMethod = System.Reflection.MethodBase.GetCurrentMethod().Name;
    }
    private string _originalMethod;
    public string OriginalMethod { get { return _originalMethod; } }
}

//now, create tons of custom exceptions:
public class MyException1 : BaseException {
    public MyException1()
        : base() {
    }
}
//create more custom exceptions...
public class MyExceptionNNN : BaseException {
    public MyExceptionNNN()
        : base() {
    }
}
1 голос
/ 30 марта 2011

Пока вы не глотаете исключения, а затем выбрасываете новое исключение (как в плохом примере ниже), у вас должно быть все в порядке.

try
{
    ... your code
}
catch (ExceptionA exA)
{
    ... some error handling
    throw new ExceptionZ();
}
catch (ExceptionB exB)
{
    ... some error handling
    throw new ExceptionZ();
}

Этот код в данном случае плох, поскольку выдает исключениезаменяет трассировку стека из исходного исключения.Во всяком случае, это не очень хорошая практика, но в этом случае вы не сможете найти и зарегистрировать свои исключения однозначно.

0 голосов
/ 30 марта 2011

В вашем примере (и в большинстве случаев на самом деле) я бы выбрасывал два различных пользовательских типа исключений, в зависимости от того, какое значение было пустым. Код, который перехватывает эти исключения, может регистрировать необходимые данные.

...