React Native Stack Traces that point to original files
Asked Answered
C

3

12

When I have an error in my React Native app, the stack trace that is printed to the console points to index.bundle instead of the original source code (see example below). Is there a way to configure React Native to use source maps so that the logs show up correctly?

This problem only occurs when throwing an error from an asynchronous callback or something outside of rendering. If I throw an error inside a component, then the error shows the correct stack trace in the console. Interestingly, the error shows the correct stack trace all the time in LogBox.

I am running this with react-native run-android and viewing the logs through Metro. To clarify, I am trying to get this working for local debug builds, not production/release builds. Ideally the logs would show the correct stack in the console so that I do not have to symbolicate them manually or find the error in LogBox.

Example result from console.error:

Error: Connection closed
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.myapp.local&modulesOnly=false&runModule=true:261835:40)
    at forEach (native)
    at flushVolatile (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.myapp.local&modulesOnly=false&runModule=true:261833:33)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.myapp.local&modulesOnly=false&runModule=true:262065:20)
    at apply (native)

Thank you in advance!

Capper answered 27/4, 2022 at 17:26 Comment(2)
I've been fighting with this for a while. I end up having to load the bundle, jump to the line a few hundred thousand lines in, and squint at the babel-ified code to try to figure out where the original source is. It's a nightmare.Awake
@GlennMaynard Oof yeah that's annoying. I came up with a solution that works for me. Check out the answer below - hopefully it works for you tooCapper
C
2

Answering my own question. I dug into the react-native code and discovered that LogBox symbolicates stack traces by making calls to the metro development server. Instead of replicating that logic, I made the hacky solution below that ties into LogBox. I'm sure there are better ways to do this, but it works.

import { observe as observeLogBoxLogs, symbolicateLogNow } from 'react-native/Libraries/LogBox/Data/LogBoxData';

// LogBox keeps all logs that you have not viewed yet.
// When a new log comes in, we only want to print out the new ones.
let lastCount = 0;

observeLogBoxLogs(data => {
    const logs = Array.from(data.logs);
    const symbolicatedLogs = logs.filter(log => log.symbolicated.stack?.length);
    for (let i = lastCount; i < symbolicatedLogs.length; i++) {
        // use log instead of warn/error to prevent resending error to LogBox
        console.log(formatLog(symbolicatedLogs[i]));
    }
    lastCount = symbolicatedLogs.length;

    // Trigger symbolication on remaining logs because
    // logs do not symbolicate until you click on LogBox
    logs.filter(log => log.symbolicated.status === 'NONE').forEach(log => symbolicateLogNow(log));
});

function formatLog(log) {
    const stackLines = (log.symbolicated.stack || [])
        .filter(line => !line.collapse)
        .map(line => `    at ${line.methodName} (${line.file}:${line.lineNumber}:${line.column})`)
        .join('\n');
    return `Error has been symbolicated\nError: ${log.message.content}\n${stackLines}`;
}

The error appears twice in console, first as the original, second as the symbolicated version. Here's an example of the log output now:

WARN  Error: Connection closed
  Error: Connection closed
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:334491:50)
    at forEach (native)
    at flushVolatile (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:334489:43)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:334721:30)
    at call (native)
    at emitNone (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:340110:33)
    at emit (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:340191:23)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:339907:24)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:339889:30)
    at apply (native)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:347770:25)
    at drainQueue (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:347735:45)
    at apply (native)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:31681:26)
    at _callTimer (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:31605:17)
    at callTimers (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:31801:19)
    at apply (native)
    at __callFunction (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:25085:36)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:24813:31)
    at __guard (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:25039:15)
    at callFunctionReturnFlushedQueue (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:24812:21)
LOG  Error has been symbolicated
  Error: Connection closed
    at Object.keys.forEach$argument_0 (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:208:28)
    at flushVolatile (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:206:4)
    at stream.on$argument_1 (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:477:17)
    at emitNone (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:6471:4)
    at emit (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:6556:14)
    at Duplexify.prototype._destroy (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:6239:2)
    at process.nextTick$argument_0 (/Users/georgeflug/projects/logteste/node_modules/mqtt/dist/mqtt.js:6221:4)
    at Item.prototype.run (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:13143:4)
    at drainQueue (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:13113:16)
Capper answered 3/5, 2022 at 14:21 Comment(2)
Does this work for release bundled apps (production)?Petras
@EddieTeixeira No because LogBox is disabled for release builds. I do not usually inspect console output directly in prod except for errors, and for that I use BugSnag which has source map supportCapper
M
0

If you click on the stacktrace (for example in terminal) will it pull up your vscode with all the associated files and to this location? At least that you might be able to back where the issue is in code?

Marable answered 27/4, 2022 at 17:34 Comment(2)
If I Ctrl-Click on the stacktrace it opens the entire index.bundle in Chrome, even if using the VSCode terminal. It does not go to the line that failedCapper
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Kaolack
G
0

This not working in production app as React Native's production builds, symbolication is disabled by default for performance and security reasons

Gerber answered 5/9 at 5:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.