0

I recently tried setting up a logging system for my Web API. I decided that I wanted to create custom Exception classes that could be thrown and then logged by my GlobalExceptionHandler. However, I am having some issues accessing the StackTrace of my custom Exception objects.

I set up a custom Exception class called LoggableException which I use as a parent class for all of my custom Exceptions that I want to log to my database.

    public class LoggableException : Exception
    {
        public LogEntity Log { get; set; }
        public string ResponseMessage { get; set; }

        public LoggableException(LogEntity _log, string _responseMessage) : base(_log.ExceptionMessage)
        {
            Log = _log;
            Log.Level = LogLevel.Warn;
            Log.Exception = GetType().Name;
            Log.StackTrace = StackTrace.Substring(0, 1000);

            ResponseMessage = _responseMessage;
        }
    }

Here is an example of one of my custom LoggableExceptions:

    public class InvalidPassword : LoggableException
    {
        public InvalidPassword(string email) : base(new LogEntity()
        {
            ExceptionMessage = $"Invalid password. email: {email}",
            ResponseCode = (int) HttpStatusCode.Unauthorized
        }, $"Unknown email or incorrect password.")
        { }
    }

However, it seems that whenever I throw one of these LoggableExceptions, a <System.NullReferenceException: Object reference not set to an instance of an object. is also thrown at Log.StackTrace = StackTrace.Substring(0, 1000);. I'm doing this so that I don't store huge StackTrace strings in my database, but StackTrace is always null. I assumed that if I called the base constructor that it would automatically instantiate StackTrace, but I guess I was wrong. Can anyone explain to me what I am doing wrong here? I am still relatively new to C#.

2
  • 1
    Using a custom exception type just for logging is questionable; actually logging in the constructor of that exception is fatally flawed (as you've discovered). The stack trace is not available because, at the time of construction, the exception hasn't even been thrown yet -- stack unwinding to find the handler is only done at that point. The exception handler(s) should do any logging required, not the exception itself. For specialized logging, just use the logger directly at the call site (with the help of the StackTrace class if you want), don't make the exception do it. Commented Sep 8, 2020 at 12:05
  • Ok, that makes total sense.Thanks for the help! Commented Sep 8, 2020 at 21:23

2 Answers 2

1

The NullReference exception is thrown because you are accesing the StackTrace property of the instance you are building inside the constructor itself.

You should use

 Log.StackTrace = StackTrace?.Substring(0, 1000);

or

 if(StackTrace!=null) Log.StackTrace=StackTrace.Substring(0, 1000);

instead to be sure that this.StackTrace is already instantiated

You should take care from the ArgumentOutOfRangeException that can be thrown when StackTrace length is lesser than 1000 characters

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

1 Comment

Checking if StackTraceis not null does nothing to instantiate it, though. That will simply mean you'll never have a stack trace, instead of getting an error -- but that can also be achieved by just leaving out this line altogether.
0

This is quite old, but there was no actual solution posted yet. I also wanted to automatically create a system log on each new custom exception (even if it's not "best practice"), this is what worked for me.

Although the Stacktrace ain't yet initialised in your constructor, you can create a new instance of StackTrace.

public class LoggableException : Exception
{
    public LogEntity Log { get; set; }
    public string ResponseMessage { get; set; }

    public LoggableException(LogEntity _log, string _responseMessage) : base(_log.ExceptionMessage)
    {
        Log = _log;
        Log.Level = LogLevel.Warn;
        Log.Exception = GetType().Name;
        Log.StackTrace = (new System.Diagnostics.StackTrace()).ToString();
        if (Log.StackTrace.Length > 1000)
            Log.StackTrace = Log.StackTrace.Substring(0, 1000);

        ResponseMessage = _responseMessage;
    }
}

This should do what you want.

The only problem is that your LoggableException constructor will also jerk its way in there (Frame zero), but you can always loop all frames to create your Stactrace String, formating the data as you want and skipping frame zero.

Comments

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.