Skip to content

Stack Trace missing for NativeAOT App #3439

Closed

Description

@emrys90

Package

Sentry

.NET Flavor

.NET Core

.NET Version

8.0

OS

Linux

SDK Version

4.7.0

Self-Hosted Sentry Version

24.6.0

Steps to Reproduce

I'm having an issue where the client is not exporting stack traces to the API for a NativeAOT compiled app. Works fine in a non-NativeAOT app.

Expected Result

Event is sent with stack traces.

Actual Result

Event is sent with no stack traces.

Activity

  1. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Jun 20, 2024
  2. jamescrosswell commented on Jun 20, 2024

    @jamescrosswell
    Collaborator

    I played around to try to reproduce this with the following program. There is a stack trace, but it only has one frame, and that doesn't appear to be anything to do with Sentry (note the Console.WriteLine statements in the catch block):

    SentrySdk.Init(options =>
    {
        options.Dsn = "...YOUR DSN HERE...";
        options.IsGlobalModeEnabled = true;
        options.TracesSampleRate = 1.0;
    });
    
    Console.WriteLine("Starting transaction...");
    
    var transaction = SentrySdk.StartTransaction("Program Main", "function");
    SentrySdk.ConfigureScope(scope => scope.Transaction = transaction);
    
    await ParentFunction();
    
    transaction.Finish();
    Console.WriteLine("Transaction Finished!");
    return;
    
    async Task ParentFunction()
    {
        await Task.Delay(500);
        await RecursiveFunction(5);
    }
    
    
    async Task RecursiveFunction(int depth)
    {
        var span = transaction.StartChild("function", nameof(RecursiveFunction));
    
        try
        {
            // Throw an exception
            if (depth <= 0)
            {
                throw new ApplicationException("Something happened!");
            }
            await RecursiveFunction(depth - 1);
            span.Finish();
        }
        catch (Exception exception)
        {
            // This is an example of capturing a handled exception.
            Console.WriteLine($"Capturing exception: {exception.Message}");
            Console.WriteLine(exception.StackTrace);
            SentrySdk.CaptureException(exception);
            span.Finish(exception);
        }
    }

    And what I see as output:

    Starting transaction...
    Capturing exception: Something happened!
       at Program.<>c__DisplayClass0_0.<<<Main>$>g__RecursiveFunction|3>d.MoveNext() in /Users/jamescrosswell/code/ConsoleAot/Program.cs:line 35
    Transaction Finished!
    

    This appears to be a limitation of AOT then.

  3. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Jun 20, 2024
  4. jamescrosswell commented on Jun 20, 2024

    @jamescrosswell
    Collaborator

    Ah, interestingly if I remove the try...catch block and just let the exception surface, we capture the native exception and for that we get a full stack trace.

    SentrySdk.Init(options =>
    {
        options.Dsn = "...YOUR DSN HERE...";
        options.IsGlobalModeEnabled = true;
        options.TracesSampleRate = 1.0;
    });
    
    Console.WriteLine("Starting transaction...");
    
    var transaction = SentrySdk.StartTransaction("Program Main", "function");
    SentrySdk.ConfigureScope(scope => scope.Transaction = transaction);
    
    ParentFunction();
    
    transaction.Finish();
    Console.WriteLine("Transaction Finished!");
    return;
    
    void ParentFunction()
    {
        RecursiveFunction(5);
    }
    
    
    void RecursiveFunction(int depth)
    {
        var span = transaction.StartChild("function", nameof(RecursiveFunction));
    
        // Throw an exception
        if (depth <= 0)
        {
            throw new ApplicationException("Something happened!");
        }
        RecursiveFunction(depth - 1);
        span.Finish();
    }

    And here's the output from that:

    Starting transaction...
    Unhandled exception. System.ApplicationException: Something happened!
       at Program.<>c__DisplayClass0_0.<<Main>$>g__RecursiveFunction|3(Int32 depth) in /Users/jamescrosswell/code/ConsoleAot/Program.cs:line 32
       at Program.<>c__DisplayClass0_0.<<Main>$>g__RecursiveFunction|3(Int32 depth) in /Users/jamescrosswell/code/ConsoleAot/Program.cs:line 32
       at Program.<>c__DisplayClass0_0.<<Main>$>g__RecursiveFunction|3(Int32 depth) in /Users/jamescrosswell/code/ConsoleAot/Program.cs:line 32
       at Program.<>c__DisplayClass0_0.<<Main>$>g__RecursiveFunction|3(Int32 depth) in /Users/jamescrosswell/code/ConsoleAot/Program.cs:line 32
       at Program.<>c__DisplayClass0_0.<<Main>$>g__RecursiveFunction|3(Int32 depth) in /Users/jamescrosswell/code/ConsoleAot/Program.cs:line 32
       at Program.<>c__DisplayClass0_0.<<Main>$>g__RecursiveFunction|3(Int32 depth) in /Users/jamescrosswell/code/ConsoleAot/Program.cs:line 32
       at Program.<>c__DisplayClass0_0.<<Main>$>g__ParentFunction|2() in /Users/jamescrosswell/code/ConsoleAot/Program.cs:line 21
       at Program.<Main>$(String[] args) in /Users/jamescrosswell/code/ConsoleAot/Program.cs:line 13
    zsh: abort      ./bin/Release/net8.0/osx-arm64/ConsoleAot
    
  5. emrys90 commented on Jun 20, 2024

    @emrys90
    Author

    There are other work arounds as well for getting a stack trace on NativeAOT. If you do exception.StackTrace.ToString() it'll give a result that can be attempted to be parsed. Not sure if there's a better way to handle it or not.

    I'm switching to Sentry from BugSnag, and they did have stack traces for my NativeAOT app.

  6. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Jun 20, 2024
  7. jamescrosswell commented on Jun 20, 2024

    @jamescrosswell
    Collaborator

    If you do exception.StackTrace.ToString() it'll give a result that can be attempted to be parsed. Not sure if there's a better way to handle it or not.

    We can't really use StackTrace.ToString(). That's missing loads of information that we need to do things like generating Enhanced Stack Frames. If you did want to capture this though, you could easily do this in the options... something like:

        options.SetBeforeSend(ev =>
        {
            if (ev.Exception is { } exception)
            {
                ev.SetExtra("Stack Trace", $"{exception.StackTrace}");
            }
            return ev;
        });

    We did open a discussion with Microsoft about this and it looks like there might be something coming in net9.0 that will help.

    I'm switching to Sentry from BugSnag, and they did have stack traces for my NativeAOT app.

    I tried the sample applications a copied/pasted above with BugSnag and saw exactly the same results. If I do a try...catch followed by a bugsnag.Notify then I only see a single line in the stack trace on BugSnag. On the other hand, if I let the exception bubble up, a full native stack trace is captured (although the application obviously terminates in this case).

    If you've got an example of something that BugSnag is capturing but Sentry is not, I'd love to learn more.

  8. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Jun 20, 2024
  9. emrys90 commented on Jun 20, 2024

    @emrys90
    Author

    Hmm weird, I was just doing a Bugsnag.Notify(exception, Severity.Error) and it would capture stack traces, just not line numbers.

  10. 24 remaining items

  11. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Jul 16, 2025
  12. jamescrosswell commented on Jul 16, 2025

    @jamescrosswell
    Collaborator

    This is pretty much what we ended up doing with .NET 8 (and it continues to work on .NET 9):

    Awesome, thanks @filipnavara !

    btw is this just for performance reasons?

    if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported)

    I assume if you removed that, stackTrace.GetFrame(0)?.GetMethod() would return null in a trimmed application regardless... but creating a stack trace is reasonably expensive.

    We were using almost exactly this technique to detect whether trimming was enabled or not up until a recently. In the 5.12.0 release we've got a source generator that detects build properties and makes these available at runtime... I'm still a little bit nervous about our logic for inferring whether trimming is enabled or not based on the build properties though... It's per the docs but I'm worried there are edge cases. Potentially what you have (and what we had before) is more reliable.

  13. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Jul 16, 2025
  14. added a commit that references this issue on Jul 17, 2025
    2dff37f
  15. filipnavara commented on Jul 18, 2025

    @filipnavara
    Contributor

    I assume if you removed that, stackTrace.GetFrame(0)?.GetMethod() would return null in a trimmed application regardless... but creating a stack trace is reasonably expensive.

    System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported is performance optimization. We know it's never going to be true for NativeAOT runtime. It can be true/false for other runtimes depending on various options.

    There's an important difference between trimmed and NativeAOT. Trimming can be performed with MonoVM (mobile workloads) and CoreCLR too (PublishTrimmed=true). Both of these case will come with full reflection support for the code that didn't get trimmed and hence stackTrace.GetFrame(0)?.GetMethod() != null there.

  16. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on Jul 18, 2025
  17. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on Jul 22, 2025
  18. added
    .NETPull requests that update .net code
    on Jul 22, 2025
  19. added a commit that references this issue on Jul 24, 2025
    3947066
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETPull requests that update .net codeAOTBugSomething isn't working

    Projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions