Debug logging for production

If you’re developing a component and including checks for scenarios where the component is passed an unexpected prop, you’ll likely want to make sure that use case isn’t used in production. Depending on the type of component and what you are aiming to do, throwing an error could be an ideal option, given that the only people that should be seeing that error are those making use of the component for their own use-cases.

Similarly, in order to make sure the component is working as it should, and to make it easier on future developers who want to make use of the component, you may want to add some form of logging when hitting certain sections of the code.

Adding a console.error may be a reasonable approach if you’re hitting a section of code that shouldn’t be hit. But that won’t be ideal if you want to sharing a debug log of sorts visible for that developer who is building a new component using your component. An option here is to use the NPM debug package.

In this article we’ll look at using the package to add logging to the browser console in particular.

Installing and adding the debug library to your code

Before we can start using the debug library, it first needs to be installed following the directions on the NPM package page.

For an imaginary scenario where we could use this, an example could be a component that works with the DOM to display a button after certain user actions. Let’s say that button component is used by an interactive component, which uses React’s createRef to create a reference for an HTML element. In JSX / Typescript, we may have:

myHTMLElementRef = React.createRef();

Then within the interactive component, let’s use the ref in the following way:

    return (
    <div>
        <div
            className="interactive-element-container"
            ref= { this.myHTMLElementRef }
        >
            <div className="interactive-element" />
        </div>
        <MyButton
            className="my-button"
            showDelay={ 100 }
            context={ this.myHTMLElementRef.current }
            isVisible={ this.state.showButton }
        >
    </div>

Let’s say that in our button component we have a check to see if the relevant context is a HTML Element inside componentDidUpdate:

this.buttonRef = createRef();

componentDidUpdate( prevProps ) {
    if ( this.props.context !== prevProps.context ) {
        if ( ! this.props.context || context instanceof HTMLElement ) {
            this.buttonRef.current = context;
        } else {
            this.debug( 'Expected an HTML Element.' );
        }
    }
}

Notice in the else block, the use of this.debug. We can set this up by importing debugFactory at the too of the file with other imports (JSX / Typescript file in this example):

import debugFactory from 'debug';

and then creating a module variable:

const debug = debugFactory( 'my-project:my-button' ); 

The debugging in action

When testing the component out, in your browser you can open up your browser console. In the console, set the localStorage key debug to the namespace that you used: localStorage.debug = 'my-project:my-button';. Then, refresh the page. This will enable the debug logs and you should see them in the browser’s console. If not, then you may need make sure your console is set to show ‘verbose’ logs (check for filtering options within the console).

What you should see is something similar to this – the project name, the string you expect to see, and the time spent between debug calls.:

my-project:my-button Expected an HTML Element. +123ms

Testing debug calls

Let’s take this one step further: If you are using debug calls in sections of code that you want to be able to test for, you can implement this with a Jest test.

Here’s a demo test file setting this up:

import userEvent from '@testing-library/user-event';
import debugFactory from 'debug';
import { render, screen, waitFor } from 'test/test-utils';
import MyButtonTest from '../my-button-test';

jest.mock( 'debug', () => {
    const debug = jest.fn();
    return jest.fn( () => debug );
} );

describe( 'MyButtonTest', () => {
    let debug;

    beforeEach( () => {
        debug = debugFactory( 'calypso:popover' );
    } );

    afterEach( () => {
        jest.clearAllMocks();
    } );

    it( 'should not show Button when context is not an HTML element', async () => {
        render( <MyButtonTest context={ {} } /> );
        await userEvent.click( screen.getByText( 'Div Context', { selector: 'div' } ) );
        await waitFor( () => {
            expect( screen.queryByText( 'Button Text' ) ).not.toBeInTheDocument();
        } );

        const debugCalls = debug.mock.calls.flat();
        const containsDebugString = debugCalls.some(
            message =>
                typeof message === 'string' && message.includes( 'Expected an HTML Element.' )
        );
        expect( containsDebugString ).toBe( true );
    } );
} );

Pretty nice isn’t it.

There are various configuration and usage options with the debug package depending on your needs, but having just set this up recently and having not used it before I thought it was a handy way to be able to output some comprehensive logs without affecting sites or applications in production.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Karen Attfield

Subscribe now to keep reading and get access to the full archive.

Continue reading