I am using an error boundary component to catch react errors and it works fine.
My problem is that in the production app the logging is kind of useless since the component stack looks like this:
\n in t\n in t\n in t\n in t\n in t\n in div\n in t\n in u\n in n\n in t\n in t
While in development environment the component stack is way more usable:
in ErrorPage (created by Route)\n in Route (at Routes.js:60)\n in Switch (at Routes.js:46)\n in Router (created by BrowserRouter)\n in BrowserRouter (at Routes.js:45)\n in div (at Routes.js:43)\n in ThemeProvider (at theme.js:1262)\n in Theme (at Routes.js:42)\n in Provider (at Routes.js:41)\n in ErrorBoundary (at Routes.js:40)\n in Routes (at index.js:12)
The same happens with the message. In production we get:
t.value (http://localhost:3333/static/js/main.5a3e606e.js:1:680858
While in dev:
Uncaught TypeError: Person is not a constructor
at ErrorPage._this.click2 (ErrorPage.js:12)
Is there a way to make react errors map to the source code and make the logging actually usable in production?
UPDATE: I am using a library called http://js.jsnlog.com/ that handles the logs and actually catches everything (Even event handlers). This is how the Boundary component looks like https://pastebin.com/aBFtD7DB. The problem is not catching the errors, but that in production they are useless.
A class component becomes an error boundary if it defines either (or both) of the lifecycle methods static getDerivedStateFromError() or componentDidCatch() . Use static getDerivedStateFromError() to render a fallback UI after an error has been thrown. Use componentDidCatch() to log error information.
In order to use Error Boundary in Functional Component, I use react-error-boundary. When we run this application, we will get a nice error display form the content of ErrorHandler component. React error boundary catches any error from the components below them in the tree.
Now that Error Boundaries are available since React version 16, it's generally advisable to use at least one Error Boundary at the root of your app (e.g., the App. js file). This will prevent users from seeing a blank HTML page and perhaps see a nice fallback UI instead.
I found a solution to this using the library https://www.stacktracejs.com/.
The method StackTrace.report() method will fetch the map and get you the unminified info you need!
So now my React Boundary looks like this. I still use window.onerror to make sure I catch everything.
First, make sure to add stacktrace-gps
and stacktrace-js
to your package.json
import React, { Component } from "react";
import StackTrace from "stacktrace-js";
window.onerror = function(msg, file, line, col, error) {
StackTrace.fromError(error).then(err => {
StackTrace.report(
err,
`//${window.location.hostname}:${process.env.REACT_APP_LOGGER_PORT || 3334}/jsnlog.logger`,
{
type: "window.onerror",
url: window.location.href,
userId: window.userId,
agent: window.navigator.userAgent,
date: new Date(),
msg: msg
}
);
});
};
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { error: null };
}
componentDidCatch(error, errorInfo) {
this.setState({ error });
StackTrace.fromError(error).then(err => {
StackTrace.report(
err,
`//${window.location.hostname}:${process.env.REACT_APP_LOGGER_PORT || 3334}/jsnlog.logger`,
{
type: "React boundary",
url: window.location.href,
userId: window.userId,
agent: window.navigator.userAgent,
date: new Date(),
msg: error.toString()
}
);
});
}
render() {
if (this.state.error) {
//render fallback UI
return (
<div className="snap text-center">
<p>We're sorry — something's gone wrong.</p>
<p>Our team has been notified</p>
</div>
);
} else {
//when there's not an error, render children untouched
return this.props.children;
}
}
}
export default ErrorBoundary;
First, it is important to create source map. I did this by adding the devtools in webpack configuration for creating source map. Brief snippet of it is as follows:
devtools: "source-map",
new UglifyJsPlugin({
sourceMap: true
})
Once source maps were created, I used the library https://www.stacktracejs.com/.
However, to reduce the bundle size on production, I didn't import the whole bundle of stacktrace. I implemented by seperating client side code and server side.
Client Side: I imported error-stack-parser. This creates an object, which contains filename, line number, column number and function name. I send the object created using this to server.
import ErrorStackParser from "error-stack-parser";
componentDidCatch(error) {
let params = {stackframes: ErrorStackParser.parse(error)};
let url = 'https://example.com';
axios.post(url, params)
}
On the server side, I imported "stacktrace-gps" and "stackframe" and used it to find it, to get the line number and column of the actual code from the source map.
const StackTraceGPS = require("stacktrace-gps");
const request = require("request");
var logger = function(req, res) {
let stackframes = req.body.stackframes;
let stackframe = new StackFrame(
stackframes[0]
); /* Getting stack of the topmost element as it contains the most important information */
/* We send extra ajax function to fetch source maps from url */
const gps = new StackTraceGPS({
ajax: url => {
return new Promise((resolve, reject) => {
request(
{
url,
method: "get"
},
(error, response) => {
if (error) {
reject(error);
} else {
resolve(response.body);
}
}
);
});
}
});
gps.pinpoint(stackframe).then(
info => {
console.log(info); /* Actual file Info*/
},
err => {
console.log(err);
}
);
};
This reduces the bundle size, and gives you the ability to log error on server side.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With