Throughout this series, we’ve covered logging in C#, Java, Python, Ruby, Go, JavaScript, PHP, Swift, and Scala. We’ve also explored a few libraries and platforms, such as Log4j, Node.js, Spring Boot, Rails, and Angular. Today we’re going to be looking at logging in React.
In this article, we’ll
- Set up a simple React application
- Work through an example of simple logging
- Discuss what logging is and why it’s important
- Explore some other logging methods that are available to us.
Let’s get started!
Some Background Information
React, also known as React.js or React JS, is a popular front-end development library for creating user interfaces. It was originally developed for Facebook and released in 2013.
React uses JavaScript as its core language. It allows for all the interactiveness we would expect from a JavaScript application, but with increased efficiency through something called a virtual DOM. At a very basic level, this allows only those components that have been affected to reload when changes are made, rather than the entire page. It does this by using an in-memory cache to compare a DOM object to its corresponding virtual DOM object and re-rendering only those that are different.
Because React is a JavaScript library, this means that we once again run into the issues of client-side logging, as we saw when we covered JavaScript. We’ll review this again after we’ve set up our simple logging application.
A Simple React Application
To create the simplest possible React application, we’ll be using Facebook’s Create React App. Before we get started though, let’s make sure we have all the necessary tools.
Setting Up Our Package Manager
In order to use Create React App, you’ll need a package manager, such as npx, npm, or Yarn. In this tutorial, we’ll be using npm, but any one of these three options will work.
If you’re not sure whether you already have npm installed, open up a command line tool such as the Windows Command Prompt or PowerShell (Windows) or the Terminal app (MacOS). Use this command:
npm -v
If npm is already installed, you’ll get a version number back. For example, 6.5.0 is the version number that was returned when I ran the command, and it’s the version I’m using for this tutorial. If you get something similar back, you’re good to go!
If you don’t get a version number back, you’ll need to install npm first. Check out the npm website to get the official instructions. In short, you’ll need to download an installer from the Node.js website and run it. This is because npm is distributed with Node.js. Once you’ve completed the installation, simply restart your computer and you’re all set!
Downloading the Create React App
Now that we have npm, we’re ready to create our React application. In your command line tool, you’ll enter the following command:
npm init react-app my-app
This downloads the code we need from the Create React App page linked above and places it inside a directory called my-app. You’ll find the new directory inside your current directory.
Let’s see what it looks like before we make any new changes. Use this command:
cd my-app
And that will move the application into the newly created folder. Once inside, you can run the application by using the following:
npm start
Next, you’ll need to open up a browser window and navigate to http://localhost:3000
. You’ll be able to see your application running locally. It should have some text and a spinning React logo. Not a lot going on here, but we’ve got all the groundwork set up to do some simple logging!
If you want some more detailed information on how Create React App works, you can check out the README here.
Logging to the Console
The simplest way to log in React is by printing out to the console. Inside the new my-app folder you should see another folder named src. Inside, you’ll see a file named App.js. Open it now inside the IDE or text editor of your choice. As expected, it’s very short, with just a div containing a header with an image, a paragraph, and a link. We won’t bother with changing any of the existing code right now, but this is where you’d start if you wanted to expand this simple app into something more complex. Instead, we’re just going to add a button underneath the link by adding the following code:
<div> <button onClick={this.logSomething}>Log Something!</button> </div>
Don’t forget to add the corresponding function outside the render block.
logSomething = () => { console.log("Button was clicked.") }
Here’s what your finished App.js file should look like once you’re done:
You’ll notice that the app inside your browser updates as soon as you save your changes. It should look something like this:
To open the console, right-click inside the application window, click Inspect, and select the Console tab. You might also be able to use the F12 key to do the same thing.
With the console open, you’ll be able to see anything that gets printed out when the button is pressed. Click on the button now, and you should see a message printed out into the console.
And there you have it! You’ve created a simple logging application in React!
What’s Logging?
Before we go any further, let’s make sure we have a solid understanding of what we mean when we talk about logging.
At the very beginning of this series, we defined logging, or application logging, as follows:
Application logging involves recording information about your application’s runtime behavior to a more persistent medium.
There are two things to note here. The first is that logging provides us with information about what the application does as it’s running.
The second is that we want our logs to stick around for a while rather than disappearing immediately. The level of persistence we need may vary depending on the application, but at the very least we want to be able to read the logs even if the application crashes or the server goes down.
Now that we know what logging is, let’s make sure we understand why we would want to go to all the trouble of adding logging to our applications.
What’s the Motivation for Logging?
Once our software is out in the world and away from the safe confines of our local debug environment, we need a way to investigate issues and analyze performance. Put simply, logging allows us to record the behavior of our application and analyze it later. With so many things that could go wrong, logged data allows us to pinpoint what error occurred and even retrace the steps the user took to create or discover the issue.
Furthermore, by logging data and analyzing it later, we become familiar with what normal behavior looks like and may even be able to detect and prevent problems before they occur. Logging—and more importantly, logging the right data—provides us with a strategy for maintaining the health of our application.
What Should We Be Logging?
Now that we know why logging is important, we need to decide what information to include. Randomly dumping data into a log file makes finding the data we actually need tedious and difficult. On the other hand, we want to provide enough information so that we can efficiently solve issues that arise. So what should we be logging?
We can start by conceptually thinking of each log entry as an event. An event is something of interest that happened at a particular instance in time. Our goal is to capture the information needed in order to understand the event as it occurred. Here’s a list of some information that would be useful to capture:
- A timestamp. Timestamps tell us when an event took place and when it occurred in relation to other events. Best practices suggest logging timestamps in a standard timezone, such as UTC, and using a standard format, such as ISO-8601.
- Context. We want to make it as clear as possible what the event is about. Trying to debug an issue using vague logged data or error names can be extremely frustrating. Providing a good description of the context of the error makes understanding and correcting it a lot more efficient.
- Log levels, such as Error, Warning, or Information. These provide more context for understanding the issue and its severity. They allow us to prioritize and focus first on those issues that have the greatest impact. They also allow us to filter through our data and analyze it more effectively.
Consider this list as a baseline of information that would be helpful to log. Obviously, our simple application above falls short of these basic guidelines.
Issues With Client-Side Logging
Our simple demonstration of React logging may have provided us with some information about the application’s behavior while running, but our logged information definitely wasn’t persistent. If we were to refresh the browser or navigate to another page, the information would disappear.
More importantly, our application was logging information to the console of the browser. If this application was released into production rather than just running locally, we would have no way to access those logged messages.
With many of the technologies we’ve explored throughout this series, we were able to use the file system to satisfy the need for a persistent medium. However, as was the case when we looked at JavaScript, that approach won’t work with React. There are several security issues involved in accessing the file system from the browser, and accessing local storage on unknown and inaccessible machines is not an option.
Extending Our Logging Application
Logging to the console has some benefit, mainly for debugging locally. But to get the full benefit of logging our data, we need something better. We need to find a way to satisfy the requirements outlined by the definition of logging given earlier:
- Access to the logged information via the server and not the browser
- The ability to store our data using a persistent medium
In short, we need a way to send messages to a server and write them to a persistent medium. Although this sounds simple, the work involved in creating a suitable mechanism is actually quite substantial. We would need to write a separate application with an available API to accept logging messages.
Rather than bogging ourselves down with the numerous tasks involved in creating something from scratch, we can instead explore another option—namely a logging framework.
A logging framework is a computer data logging package with methods allowing for logging at different levels. Fortunately for us, there are several existing options that satisfy our requirements.
Exploring a Logging Framework
For the purposes of this tutorial, we’ll be looking at universal-react-logger. The description given states that it’s
A logger to catch client errors on the server.
In addition, events triggered in the browser are sent to the server. From there, they can be printed out immediately or sent to external log services for persistent storage. Both our requirements are satisfied!
How does universal-react-logger work? It takes advantage of error boundaries, which were introduced with React 16. Error boundaries are primarily used to catch errors during rendering but have been extended here to catch event errors as well.
Working With Universal React Logger
Now let’s start implementing our logging framework. Once again, let’s make sure we have all the requirements in order first.
Requirements and Installation
As you might expect, universal-react-logger requires a React version of at least 16.2.0. To find out what version your project is running, find the node_modules folder inside your my-app application directory. Find /node_modules/react/package.json and look for the version key:
If your version of React is below 16.2.0, use the following command to upgrade:
npm update
Additionally, universal-react-logger requires React Router DOM with a version of at least 4.2.2. Install it with this command:
npm install --save react-router-dom
Finally, install universal-react-logger:
npm i universal-react-logger
Create a New Component
Now that we’re set up, we can create a new component and import the ErrorHandler HOC to use with it. The example below is based on the code provided on the universal-react-logger website. We’ll start by creating a new file called Homepage.js and importing ErrorHandler from universal-react-logger.
import { ErrorHandler} from 'universal-react-logger';
Next, we’ll add a constructor to our Homepage component to set up our state and bind a few new functions.
We’ll also need to create the functions mentioned in the constructor, starting with the function that updates the counter:
The second function pretends to call a function that doesn’t exist, resulting in an event error. It then sends the error using the setEventError function.
Lastly, we’ll update the render block. We’ll add a condition to simulate a render error when the counter is equal to five, and we’ll also have two buttons this time. One button will allow us to update the counter so we can eventually create a render error. The other will trigger an event error. The completed file should look like this:
import React, { Component } from 'react'; import { ErrorHandler} from 'universal-react-logger'; class Homepage extends Component { constructor(props) { super(props); this.state = { counter: 0, error: this.props.error, errorInfo: this.props.errorInfo }; this.handleClick = this.handleClick.bind(this); this.makeError = this.makeError.bind(this); } handleClick() { this.setState(({counter}) => ({ counter: counter + 1 })); } makeError () { try{ // pretend to call a function that does not exist this.functionThatDontExist(); } catch(error) { // send the error using the setEventError function this.props.setEventError(error); } }; render() { if (this.state.counter === 5) { // Simulate a render error throw new Error('Error on render'); } return ( <div> <h1 key="welcome">universal-react-logger</h1> <button onClick={this.handleClick}>Update counter: {this.state.counter}</button> <button onClick={() => {this.makeError()}}>Make event error</button> </div> ); } } export default ErrorHandler(Homepage, true);
Create a Route on the Server
With our component ready, we also need to add the corresponding route on the server. Inside the src folder, you’ll find the index.js file. Open it and add the following:
/** * Post client errors in order to log them */ app.post('/log-client-errors', (req, res) => { let error = req.body.error.message; let errorInfo = req.body.error.stack; // send these errors to some service or to a logger (ex: winston) //ex: logger.error(`The app received a new client log: ${error} ${errorInfo}`); res.status(200); });
Now when we run the application with the server, we’ll see our log messages printed to the console when we trigger an error. This means we’ve successfully sent a logging message from the browser back to the server!
What’s Next?
Even with all the work we’ve done here, we’ve barely scratched the surface of logging in React. In particular, sending logging information from the client-side back to the server involves a whole new set of variables to consider. For example, browser information, URLs, and device information might be valuable information to transmit. The features and capabilities of different logging frameworks are also worth your consideration and should be influenced by an understanding of what information you need.
No matter what you decide, you’ve undoubtedly recognized that client-side logging isn’t enough. It can be helpful to get an impression of the overall behavior of your application, but to be really useful, you want to consider a way to get your logs back to the server.
Knowing where to start with logging can be a daunting task, but having clear and detailed insight into the functioning of your software will be well worth the effort. Once you get to the point where you’re collecting a considerable amount of data, you may want to organize it, search it, and represent it visually. You’d most likely benefit from matching client-side messages to server-side messages as well. If that’s the case, your next step is to consider log aggregation, which happens to be Scalyr’s specialty.
To read more about the things you can do with your data, check out what we have to say about log aggregation! But in the meantime, start with a basic level of logging and build from there.