georgeflug
georgeflug

Reputation: 151

React Native Stack Traces that point to original files

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!

Upvotes: 12

Views: 3970

Answers (2)

georgeflug
georgeflug

Reputation: 151

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)

Upvotes: 3

Wade
Wade

Reputation: 55

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?

Upvotes: 0

Related Questions