24

We have a service which will log unhandled exceptions at the app domain level (via Log4net).

We logged:

2014-01-28 16:49:19,636 ERROR [49] FeedWrapperService - unhandled System.NullReferenceException: Object reference not set to an instance of an object.

This exception has no stack trace. How is that possible without doing some crazy things to the exception object?

Our handling code:

AppDomain.CurrentDomain.UnhandledException += LogAnyExceptions;

private void LogAnyExceptions(object sender, UnhandledExceptionEventArgs e)
{
    log.Error("unhandled", (Exception)e.ExceptionObject);
    throw (Exception)e.ExceptionObject;
}

It occurs to me that the re-throw here is pointless because the AppDomain will come down along with the process anyway, but I don't think it affects our situation.

The windows application event viewer also shows only this null ref exception and no trace.

I've tested the exception handler logging and it successfully logs the stack trace and any inner exception. If it was thrown by our code, we would see a stack trace. If it was thrown by the 3rd party c# library then, again, we would see a stack trace of at least one method (whether it was a re-thrown exception or not). Here we see a managed exception with no stack trace. I don't know how this is possible.

Looking at the decompiled 3rd party library it talks to unmanaged code, and the event that raised this exception is likely in unmanaged land, but how could such a situation cause a managed null ref exception without a stacktrace?

The cause of this problem is intermittent. We've been running this code in production for several months and seen it do this once. It's pretty freaky.

The general consensus is that the system responsible for this kind of problem should be pushed off into a child process so we can deal with the problem and restart safely and automatically, but it would be nice to know what's going on.

Edit to include comment info from below:

My exception is not a standard re-throw, because the stack trace is either null or empty. It does not have the name of the re-throwing method in it. Digging further, the Exception class can be constructed from serialized info, and it looks like the serialized info could contain null strings for stack trace, and potentially that could be created without causing other errors. I guess it might come from there, but I don't know how it originated.

3
  • 1
    This thread may help Commented Jan 31, 2014 at 12:12
  • 1
    Finger poking: try to check Environment.StackTrace and perhaps inner exception? Commented Jan 31, 2014 at 12:24
  • @Andrei that's an interesting read, but my exception has not even one method in its stack trace, which I would have if it was a normal re-throw. Digging further, the Exception class can be constructed from serialized info, and it looks like the serialized info could contain null strings for stack trace without causing errors. I guess it might come from there, but I don't know how it originated. Commented Jan 31, 2014 at 13:35

5 Answers 5

13

If you're receiving an exception but no corresponding stack trace then at some point an exception handler is probably evaluating the exception and re-throwing it incorrectly. For example, if you're doing a throw ex; you'll eat the stack trace that led to that point. To preserve the existing call stack you want to simply throw; Throwing exceptions best practices

Note that the C# way is the opposite of the convention for the Java language, where you are supposed to throw ex; Java reference: Best Practice: Catching and re-throwing Java Exceptions

Sign up to request clarification or add additional context in comments.

1 Comment

This would not have no stacktrace, it would have a stacktrace from the re-throw location.
12

I prefer to manage my exceptions with custom exceptions, If in your case you are using your own exceptions you can go the the class were you define them and override the stacktrace. e.g:

public class YourCustomException: Exception
{
    public YourCustomException() : base() { } //constructor

    public override string StackTrace 
    {
        get { return "Message instead stacktrace"; }
    }
}

Comments

1

Since this has been open long enough, I'll take the only likely answer rather than leave it open forever :)

Namely:

The Exception class can be constructed from serialized info, and it looks like the serialized info could contain null strings for stack trace, and potentially that could be created without causing other errors. I guess it might come from there, but I don't know how it originated.

My only other thought was that perhaps the rest of the exception information could not be written to the log file before the app domain was torn down. But since the same was displayed in the Event Viewer and I assume that the whole event is created atomically, I think that's not how it happened.

Comments

0

Create common exception type like this:

/// <summary>
/// Special kind of exception, which is known and no need to report call stack on it's failure.
/// </summary>
class UIException: Exception
{
    public UIException(string message): base(message)
    { 
    }

    /// <summary>
    /// Don't display call stack as it's irrelevant
    /// </summary>
    public override string StackTrace
    {
        get {
            return "";
        }
    }
}

And then just throw new UIException("failure message");

1 Comment

That's a good suggestion. In this case though, it was a System.NullReferenceException
0

When you get an AggregateException then the stack trace is null. This kind of exception is like a bucket, which can contain one or more exceptions with different stack traces.

Solution for this case: Check if the received exception is such a type. If so, then process each included exception separately.

Simple example:

private void LogAnyExceptions(object sender, UnhandledExceptionEventArgs e)
{
  if ((e.ExceptionObject is AggregateException aggEx) && (aggEx.InnerExceptions != null))
    {
      foreach (Exception innerAggEx in aggEx.InnerExceptions)
      {
        // do something with innerAggEx
      }
    }
    else // single exception
    {
      // do something with e.ExceptionObject
    }
}

Extended example for more callbacks and simple crash logger:

Apply callbacks:

Note: The names/classes can be slightly different, related to .NET version and/or application type. (this example was created for WPF with .NET 4.8)

AppDomain.CurrentDomain.UnhandledException       += new UnhandledExceptionEventHandler(OnUnhandledException);
Application.Current.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(OnDispatcherUnhandledException);
TaskScheduler.UnobservedTaskException            += OnUnobservedTaskException;

Add callback methods and logger:

private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
{
  string exitInfo = (args.IsTerminating == true) ? "application crash" : "application continues";
  string info = $"Unhandled AppDomain exception ({exitInfo})";
  LogUnhandledException(info, (Exception)args.ExceptionObject);
}

private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
  string info = $"Unhandled dispatcher exception (handled: {e.Handled})";
  LogUnhandledException(info, e.Exception);
}

private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
  string info = $"Unhandled task exception (observed: {e.Observed})";
  LogUnhandledException(info, e.Exception);
}

private void LogUnhandledException(string info, Exception e)
{
  string crashMessage = $"{info} at {DateTime.Now:yyyy-MM-dd HH:mm:ss}\r\n"
            + $"Message(s): {GetAllExceptionMessages(e)}\r\n"
            + $"Exception type(s): {GetAllExceptionTypes(e)}\r\n"
            + $"Stack trace:\r\n{e.StackTrace ?? "[NULL]"}";

  if ((e is AggregateException aggEx) && (aggEx.InnerExceptions != null))
  {
    int i = 1;
    foreach (Exception innerAggEx in aggEx.InnerExceptions)
    {
      crashMessage += $"\r\n------------------------------ aggregation #{i++} ------------------------------\r\n"
             +  $"Message(s): {GetAllExceptionMessages(innerAggEx)}\r\n"
             +  $"Exception type(s): {GetAllExceptionTypes(innerAggEx)}\r\n"
             +  $"Stack trace:\r\n{innerAggEx.StackTrace ?? "[NULL]"}";
    }
  }

  // log to file
  try
  {
    string exeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string randomString = Guid.NewGuid().ToString().Substring(0, 5); // get unique file name, even if different exception callbacks are fired at the same time

    string fullFileName = Path.Combine(exeDir, $"CrashLog_{DateTime.Now:yyyy-MM-dd_HH.mm.ss_}{randomString}.txt");
    File.WriteAllText(fullFileName, crashMessage);
  }
  catch
  {
  }
}

private string GetAllExceptionMessages(Exception e)
{
  if (e == null)
    return String.Empty;
  if (e.InnerException == null)
    return e.Message;
  else
    return $"{e.Message} | " + GetAllExceptionMessages(e.InnerException);
}

private string GetAllExceptionTypes(Exception e)
{
  if (e == null)
    return String.Empty;
  if (e.InnerException == null)
    return e.GetType().Name;
  else
    return $"{e.GetType().Name} | " + GetAllExceptionTypes(e.InnerException);
}

Example crash log file:

Unhandled task exception (observed: False) at 2023-09-27 16:46:08
Message(s): Exceptions of a task were not observed. ... | Test AA | Test BB
Exception type(s): AggregateException | FormatException | ArgumentException
Stack trace:
[NULL]
------------------------------ aggregation #1 ------------------------------
Message(s): Test AA | Test BB
Exception type(s): FormatException | ArgumentException
Stack trace:
   at MyClass.MyMethod() in C:\Code\MyProject\MyDir\MyFile.cs:Line 177.
   at System.Threading.Tasks.Task.Execute()

2 Comments

The question shows that the exception in question was a System.NullReferenceException rather than an AggregateException
The title is "exception with no stack trace - how?" and it's a prominent search result on Google. So my answer is related to this more common topic / title. It's result of reading different StackOverflow questions/answers and experiments. Even a NullReferenceException can be wrapped into an AggregateException. If you just log the Message of the catched exception you get the same text. (The text of the first wrapped exception) But the StackTrace isn't wrapped to outside. Your code calls log.Error("...", ex) - maybe the log4net fells into this pitfall. Check the type yourself.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.