Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to fill in Stack trace when manually capturing an exception? (Just like what happens when auto captured) #503

Closed
1 of 3 tasks
fzyzcjy opened this issue Mar 26, 2021 · 8 comments · Fixed by #517
Closed
1 of 3 tasks

Comments

@fzyzcjy
Copy link

fzyzcjy commented Mar 26, 2021

Description

Hi thanks for the lib! I wonder how can I fill in the stacktrace? Currently when I report an error manually, it only has exception messages. But as we know, stack traces are very important to debug!

In addition, I do see automatic stack traces when an unexpected exception happens. Thus I guess sentry should have an api like get_the_current_stack_trace_as_sentry_data_structures() ;)

I have tried to use the following:

sentry_value_t event = sentry_value_new_event();
            sentry_value_set_by_key(event, "exception", exception_obj);
            sentry_event_value_add_stacktrace(event, nullptr, 0);
            sentry_capture_event(event);

but then error occurs:

2021-03-26 09:47:05.010 6468-6733/com.cjy.yplusplus E/Sentry: An exception occurred while processing event by processor: io.sentry.android.core.DefaultAndroidEventProcessor
    java.lang.NullPointerException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
        at io.sentry.android.core.util.MainThreadChecker.isMainThread(MainThreadChecker.java:40)
        at io.sentry.android.core.DefaultAndroidEventProcessor.processNonCachedEvent(DefaultAndroidEventProcessor.java:213)
        at io.sentry.android.core.DefaultAndroidEventProcessor.process(DefaultAndroidEventProcessor.java:143)
        at io.sentry.SentryClient.processEvent(SentryClient.java:185)
        at io.sentry.SentryClient.captureEvent(SentryClient.java:76)
        at io.sentry.Hub.captureEvent(Hub.java:82)
        at io.sentry.Sentry.captureEvent(Sentry.java:254)
        at io.sentry.HubAdapter.captureEvent(HubAdapter.java:27)
        at io.sentry.OutboxSender.processEnvelope(OutboxSender.java:141)
        at io.sentry.OutboxSender.processFile(OutboxSender.java:68)
        at io.sentry.OutboxSender.processEnvelopeFile(OutboxSender.java:101)
        at io.sentry.android.core.EnvelopeFileObserver.onEvent(EnvelopeFileObserver.java:59)
        at android.os.FileObserver$ObserverThread.onEvent(FileObserver.java:163)
        at android.os.FileObserver$ObserverThread.observe(Native Method)
        at android.os.FileObserver$ObserverThread.run(FileObserver.java:113)

Ok here is my hack:

    sentry_value_t threads = sentry_value_get_by_key(event, "threads");
    sentry_value_t values = sentry_value_get_by_key(threads, "values");
    sentry_value_t thread = sentry_value_get_by_index(values, 0);
    sentry_value_set_by_key(thread, "id", sentry_value_new_int32(-1));

but it still does not work. now no errors, but no stacktraces.

the output of sentry_value_to_json(event):

{"event_id":"9aabc52f-2ece-439d-f46e-3206346ba765","timestamp":"2021-03-26T02:28:09.814Z","exception":{"values":[{"type":"Exception","value":"cv_error_callback see error status=-215 func_name=debug_throw_exception err_msg=1 == 0 file_name=/Users/tom/QAPMain/Development/rzzsdxx_frontend/vision_utils/ios/Classes/basic_vision_utils.cpp line=190"}]},"threads":{"values":[{"stacktrace":{"frames":[{"instruction_addr":"0x71e6806698"},{"instruction_addr":"0x71aaf8e0a8"},{"instruction_addr":"0x71ab1369cc"},{"instruction_addr":"0x71ab136f90"},{"instruction_addr":"0x71aaf8d980"},{"instruction_addr":"0x71aaf8d44c"},{"instruction_addr":"0x71c5415cc4"},{"instruction_addr":"0x71c5418548"}]},"id":999}]}

but the website:

image

Therefore, what I want: Can we fill in Stack trace when manually capturing an exception? (Just like what happens when auto captured)? Now there is only instruction addresses - no things like function names, etc!


When does the problem happen

  • During build
  • During run-time
  • When capturing a hard crash

Environment

  • OS: [e.g. Windows 10, 64-bit] android
  • Compiler: [e.g. MSVC 19] android ndk compiler
  • CMake version and config: [e.g. 3.17.2, SENTRY_BACKEND=inproc]

Steps To Reproduce

Log output
N/A

@fzyzcjy
Copy link
Author

fzyzcjy commented Apr 11, 2021

@Swatinem Hi it seems that you are expert in this library (by looking at the "assignee" column of issues), so could you please provide some suggestions? Thanks!

@Swatinem
Copy link
Member

Hi! Sorry, this slipped my radar.

We currently have

/**
* Adds a stacktrace to an event.
*
* If `ips` is NULL the current stacktrace is captured, otherwise `len`
* stacktrace instruction pointers are attached to the event.
*/
SENTRY_EXPERIMENTAL_API void sentry_event_value_add_stacktrace(
sentry_value_t event, void **ips, size_t len);

but that will add the stacktrace to a new "thread" object. You can extract it from there and reuse it.
There is currently no better API, but we have plans to make this more convenient.

@fzyzcjy
Copy link
Author

fzyzcjy commented Apr 12, 2021

@Swatinem Thanks! However, it seems that it does not work :(

Here is my code:

void _add_stacktrace(sentry_value_t event) {
//    LOGI("add_stacktrace start event.json=%s", sentry_value_to_json(event));

    // only for debug
//    void *walked_backtrace[256];
//    size_t len = sentry_unwind_stack(NULL, walked_backtrace, 256);
//    LOGI("hello sentry_unwind_stack len=%d", (int) len);

    sentry_event_value_add_stacktrace(event, nullptr, 0);

    sentry_value_t threads = sentry_value_get_by_key(event, "threads");
    sentry_value_t values = sentry_value_get_by_key(threads, "values");
    sentry_value_t thread = sentry_value_get_by_index(values, 0);
    sentry_value_t stacktrace = sentry_value_get_by_key(thread, "stacktrace");
    sentry_value_t frames = sentry_value_get_by_key(stacktrace, "frames");

    // NOTE hack
    sentry_value_set_by_key(thread, "id", sentry_value_new_int32(999));

//    LOGI("hello frames.len=%d", (int) sentry_value_get_length(frames));

//    LOGI("add_stacktrace end event.json=%s", sentry_value_to_json(event));
}

use it:

            sentry_value_t exc = sentry_value_new_object();
            sentry_value_set_by_key(exc, "type", sentry_value_new_string("Exception"));
            sentry_value_set_by_key(exc, "value", sentry_value_new_string(combined_msg.c_str()));

            // List<SentryException> values;
            sentry_value_t values_list = sentry_value_new_list();
            sentry_value_append(values_list, exc);

            // SentryValues<SentryException> exception;
            sentry_value_t exception_obj = sentry_value_new_object();
            sentry_value_set_by_key(exception_obj, "values", values_list);

            // SentryEvent event;
            sentry_value_t event = sentry_value_new_event();
            sentry_value_set_by_key(event, "exception", exception_obj);
            _add_stacktrace(event);

            LOGI("sentry_capture_event event=%s", sentry_value_to_json(event));
            sentry_capture_event(event);

(the hack is because the Android sentry processor requires thread id. so I put a dummy one. that is not the core problem)

result: only have instruction_addr, does not have function names and function lines. but definitely we want names and line numbers, otherwise it is completely useless!

@Swatinem
Copy link
Member

The stacktrace is still in the "thread" protocol object, and not part of the "exception" protocol object. Other than that, when building for android, we should automatically resolve function names on the device at least. But serverside processing will run afterwards as well.

@fzyzcjy
Copy link
Author

fzyzcjy commented Apr 13, 2021

@Swatinem But the output of the sentry_value_to_json is as follows:

{"event_id":"9aabc52f-2ece-439d-f46e-3206346ba765","timestamp":"2021-03-26T02:28:09.814Z","exception":{"values":[{"type":"Exception","value":"cv_error_callback see error status=-215 func_name=debug_throw_exception err_msg=1 == 0 file_name=/Users/tom/QAPMain/Development/rzzsdxx_frontend/vision_utils/ios/Classes/basic_vision_utils.cpp line=190"}]},"threads":{"values":[{"stacktrace":{"frames":[{"instruction_addr":"0x71e6806698"},{"instruction_addr":"0x71aaf8e0a8"},{"instruction_addr":"0x71ab1369cc"},{"instruction_addr":"0x71ab136f90"},{"instruction_addr":"0x71aaf8d980"},{"instruction_addr":"0x71aaf8d44c"},{"instruction_addr":"0x71c5415cc4"},{"instruction_addr":"0x71c5418548"}]},"id":999}]}

On the other hand, when the app is aborted, the automatically captured exception will have very nice information (function name and line)

@fzyzcjy
Copy link
Author

fzyzcjy commented Apr 13, 2021

@Swatinem Finally I made it! I solved this problem by looking at the private method make_signal_event and made some hacks... It is really ugly :( I do hope sentry native can provide a better solution!

sentry_value_t
sentry__value_new_addr(uint64_t addr) {
    char buf[100];
    size_t written = (size_t) snprintf(
            buf, sizeof(buf), "0x%llx", (unsigned long long) addr);
    if (written >= sizeof(buf)) {
        return sentry_value_new_null();
    }
    buf[written] = '\0';
    return sentry_value_new_string(buf);
}

static sentry_value_t make_error_event(string err_msg) {
    sentry_value_t event = sentry_value_new_event();
//    sentry_value_set_by_key(event, "level", sentry__value_new_level(SENTRY_LEVEL_FATAL));

    sentry_value_t exc = sentry_value_new_object();
    sentry_value_set_by_key(exc, "type", sentry_value_new_string("Exception"));
    sentry_value_set_by_key(exc, "value", sentry_value_new_string(err_msg.c_str()));
//    sentry_value_set_by_key(exc, "type",
//                            sentry_value_new_string( sig_slot ? sig_slot->signame : "UNKNOWN_SIGNAL"));
//    sentry_value_set_by_key(exc, "value",
//                            sentry_value_new_string( sig_slot ? sig_slot->sigdesc : "UnknownSignal"));

//    sentry_value_t mechanism = sentry_value_new_object();
//    sentry_value_set_by_key(exc, "mechanism", mechanism);

//    sentry_value_t mechanism_meta = sentry_value_new_object();
//    sentry_value_t signal_meta = sentry_value_new_object();
//    if (sig_slot) {
//        sentry_value_set_by_key(
//                signal_meta, "name", sentry_value_new_string(sig_slot->signame));
//        // at least on windows, the signum is a true u32 which we can't
//        // otherwise represent.
//        sentry_value_set_by_key(signal_meta, "number",
//                                sentry_value_new_double((double) sig_slot->signum));
//    }
//    sentry_value_set_by_key(mechanism_meta, "signal", signal_meta);
//    sentry_value_set_by_key(
//            mechanism, "type", sentry_value_new_string("signalhandler"));
//    sentry_value_set_by_key(
//            mechanism, "synthetic", sentry_value_new_bool(true));
//    sentry_value_set_by_key(mechanism, "handled", sentry_value_new_bool(false));
//    sentry_value_set_by_key(mechanism, "meta", mechanism_meta);

    const int MAX_FRAMES = 128;
    void *backtrace[MAX_FRAMES];
//    size_t frame_count
//            = sentry_unwind_stack_from_ucontext(uctx, &backtrace[0], MAX_FRAMES);
    // if unwinding from a ucontext didn't yield any results, try again with a
    // direct unwind. this is most likely the case when using `libbacktrace`,
    // since that does not allow to unwind from a ucontext at all.
//    if (!frame_count) {
    size_t frame_count = sentry_unwind_stack(NULL, &backtrace[0], MAX_FRAMES);
//    }
//    SENTRY_TRACEF("captured backtrace with %lu frames", frame_count);

//    sentry_value_t frames = sentry__value_new_list_with_size(frame_count);
    sentry_value_t frames = sentry_value_new_list();
    for (size_t i = 0; i < frame_count; i++) {
        sentry_value_t frame = sentry_value_new_object();
        sentry_value_set_by_key(frame, "instruction_addr",
                                sentry__value_new_addr(
                                        (uint64_t) (size_t) backtrace[frame_count - i - 1]));
        sentry_value_append(frames, frame);
    }

    sentry_value_t stacktrace = sentry_value_new_object();
    sentry_value_set_by_key(stacktrace, "frames", frames);

    sentry_value_set_by_key(exc, "stacktrace", stacktrace);

    sentry_value_t exceptions = sentry_value_new_object();
    sentry_value_t values = sentry_value_new_list();
    sentry_value_set_by_key(exceptions, "values", values);
    sentry_value_append(values, exc);
    sentry_value_set_by_key(event, "exception", exceptions);

    return event;
}

usage:

sentry_value_t event = make_error_event("hello I am an error");
sentry_capture_event(event);

result: things like

image

@fzyzcjy
Copy link
Author

fzyzcjy commented Apr 13, 2021

Indeed, I guess the problem with my old code is the format of instruction_addr. Notice that sentry_unwind_stack fill it with integers; while sentry__symbolize_stacktrace only allows strings like 0x123456! and in sentry__symbolize_stacktrace, it says:

        size_t addr
            = (size_t)strtoll(sentry_value_as_string(addr_value), NULL, 0);
        if (!addr) {
            continue;
        }
        sentry__symbolize((void *)addr, sentry__symbolize_frame, &frame);

so int address will be thrown away...

@Swatinem
Copy link
Member

Ah yes. I think the the important thing here is:

sentry_value_set_by_key(exc, "stacktrace", stacktrace);

You are attaching the stacktrace to the exception record.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants