Skip to content

Extending the console to improve developer experience #163

Open
@bvaughn

Description

This issue is meant as a conversation starter around tools like React DevTools. The following GitHub issues provide some additional background context:

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:

Example console.error override with annotations

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.

Example console.erorr override with annotations

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:

Example of why iterative stack isn't that useful

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.

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions