Extending the console to improve developer experience #163
Description
This issue is meant as a conversation starter around tools like React DevTools. The following GitHub issues provide some additional background context:
- Remove React.error and React.warn facebook/react#16126
- Patch console to append component stacks bvaughn/react-devtools-experimental#348
- Console Logging for StrictMode Double Rendering facebook/react#22030
This issue relates to both logging and call stacks. I'll try to keep this issue mostly focused on the logging/console aspect but there will be some overlap.
Summary
At a high level, it would be nice if there were a way for frameworks or extensions to extend console methods (like console.error
) without exposing themselves in the stack that the browser captures.
I'll use React and the React DevTools as an example. We recently added a feature to auto-append some additional info- referred to as "component stack" (explained below) to error logs. We do this because the call stack alone is often insufficient to identify the cause of an error. Conceptually, I think this is similar to the async call stack feature in terms of how it helps developers identify problems and debug their code.
The only mechanism we currently have to append this information is to override the built-in console method, e.g.
const originalConsoleMethod = console.error;
console.error = (...args) => {
// Override logic...
originalConsoleMethod(...args);
};
Unfortunately this means our override method is the top stack frame, so it's observable along with the error being logged:
It would be nice if there were a way for us to tell the console to ignore, or skip over, the top most frame (our override method) like it does with its own internal methods.
It would also be nice if the appended "component stack" were an actual, clickable stack rather than just an appended string of text.
Althoguh the above example shows console.error
being called from within a component (in user code). In many cases, React itself might log what logs the error, even though it's about a component.
In this example, the relevant user code (React component) isn't even in the call stack anywhere, only in the appended "component stack". It would also be nice if there were a way for us to add it.
What are "component stacks"?
React applications are trees of components. These trees are built at runtime by "rendering" each component.
Here's an example app:
// Example.js
function Example() {
return <List items={["one", undefined, "two"]} />;
}
// List.js
function List({ items }) {
return (
<ul>
{items.map(text => (
<ListItem key={text} text={text} />
))}
</ul>
);
}
// ListItem.js
function ListItem({ text }) {
if (!text) {
console.error('Required property "text" is missing');
}
return <li>{text}</li>;
}
The above code can be thought of as a tree:
<Example>
<List>
<ListItem>
<ListItem> <- The error was here
<ListItem>
Some things of note about this tree:
- Each new component that is rendered may produce its own subtree.
- A component may appear more than once in the tree (e.g. ListItem above).
- Errors are often associated with a specific instance of a component within the tree.
With recursive code, this sort of thing more or less "just works". However because of React's concurrent rendering mode, it processes the tree iteratively. This means that at the time our example error is logged, the JavaScript callstack doesn't provide enough information to identify where the source of the problem is:
The above call stack mostly consists of React internals. While it's helpful and meaningful to those of us working on React, it isn't all that helpful to most application developers when it comes to finding the source of their problem.