Another post, another chance to talk about a pattern I’ve been using quite a while but that I don’t see being mentioned much: this time, it’s about logging conditionally.
This post will be short since the pattern is very simple. It revolves around using query parameters to enable and/or disable the logging level of your app. This is especially useful if you want to enable some logs to simplify debugging in a live environment (even production).
Nothing complex here, so a few lines of code and comments should hopefully be enough to convey the message.
/**
* This custom logger is a way to log some info to the devtools console
* conditionally, driven by a query parameter.
* This is helpful, for example, to opt-in to the logging of a specific feature
* for debugging purposes even in a live environment (e.g., production).
* To enable it, add `?custom-logger-enable` to the URL, to disable it add
* `?custom-logger-disable` to the URL.
* The logging mode is stored in the local storage so that it persists
* between page reloads.
* For the sake of simplicity, in this example, the logger can only be in two
* states: on or off. But you can go as complex as needed and use multiple log
* levels, for example.
*/
const ENABLE_LOGGER_QUERY_PARAM = "custom-logger-enable";
const DISABLE_LOGGER_QUERY_PARAM = "custom-logger-disable";
const CUSTOM_LOGGER_MODE_LOCALSTORAGE_KEY = "custom-logger-mode";
let isCustomLoggerEnabled = false;
/**
* Determine if the logger should be enabled or not by checking the query params
* and local storage.
* P.S.: Please notice that for simplicity we're running the initialization as
* soon as this file is imported. But for a more robust approach, I'd recommend
* avoiding side effects and running the initialization in an exported function
* instead that should be invoked before (once) using the custom logger.
*/
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has(DISABLE_LOGGER_QUERY_PARAM)) {
localStorage.removeItem(CUSTOM_LOGGER_MODE_LOCALSTORAGE_KEY);
} else if (urlParams.has(ENABLE_LOGGER_QUERY_PARAM)) {
isCustomLoggerEnabled = true;
localStorage.setItem(CUSTOM_LOGGER_MODE_LOCALSTORAGE_KEY, "true");
} else {
isCustomLoggerEnabled = Boolean(
localStorage.getItem(CUSTOM_LOGGER_MODE_LOCALSTORAGE_KEY),
);
}
/**
* Export a "custom" logger that behaves like `console.*` but prints to the
* output only if enabled.
*/
export const customLogger = {};
/**
* You can customize the logger as you wish.
* In this case, we're just printing a "[CustomLogger]" prefix in front of it,
* but you can also spice up things with colors, etc.
*/
const prefix = "[CustomLogger] ";
["log", "debug", "info", "warn", "error"].forEach((method) => {
customLogger[method] = function (...args) {
if (!isCustomLoggerEnabled) {
return;
}
const prefixedArgs = [prefix, ...args];
console[method].apply(console, prefixedArgs);
};
});
/**
* This custom logger is a way to log some info to the devtools console
* conditionally, driven by a query parameter.
* This is helpful, for example, to opt-in to the logging of a specific feature
* for debugging purposes even in a live environment (e.g., production).
* To enable it, add `?custom-logger-enable` to the URL, to disable it add
* `?custom-logger-disable` to the URL.
* The logging mode is stored in the local storage so that it persists
* between page reloads.
* For the sake of simplicity, in this example, the logger can only be in two
* states: on or off. But you can go as complex as needed and use multiple log
* levels, for example.
*/
const ENABLE_LOGGER_QUERY_PARAM = "custom-logger-enable";
const DISABLE_LOGGER_QUERY_PARAM = "custom-logger-disable";
const CUSTOM_LOGGER_MODE_LOCALSTORAGE_KEY = "custom-logger-mode";
let isCustomLoggerEnabled = false;
/**
* Determine if the logger should be enabled or not by checking the query params
* and local storage.
* P.S.: Please notice that for simplicity we're running the initialization as
* soon as this file is imported. But for a more robust approach, I'd recommend
* avoiding side effects and running the initialization in an exported function
* instead that should be invoked before (once) using the custom logger.
*/
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has(DISABLE_LOGGER_QUERY_PARAM)) {
localStorage.removeItem(CUSTOM_LOGGER_MODE_LOCALSTORAGE_KEY);
} else if (urlParams.has(ENABLE_LOGGER_QUERY_PARAM)) {
isCustomLoggerEnabled = true;
localStorage.setItem(CUSTOM_LOGGER_MODE_LOCALSTORAGE_KEY, "true");
} else {
isCustomLoggerEnabled = Boolean(
localStorage.getItem(CUSTOM_LOGGER_MODE_LOCALSTORAGE_KEY),
);
}
/**
* Export a "custom" logger that behaves like `console.*` but prints to the
* output only if enabled.
*/
export const customLogger = {};
/**
* You can customize the logger as you wish.
* In this case, we're just printing a "[CustomLogger]" prefix in front of it,
* but you can also spice up things with colors, etc.
*/
const prefix = "[CustomLogger] ";
["log", "debug", "info", "warn", "error"].forEach((method) => {
customLogger[method] = function (...args) {
if (!isCustomLoggerEnabled) {
return;
}
const prefixedArgs = [prefix, ...args];
console[method].apply(console, prefixedArgs);
};
});
// Usage
import { customLogger } from "./custom-logger";
customLogger.log("hello world!");
// Usage
import { customLogger } from "./custom-logger";
customLogger.log("hello world!");
I’ve seen it a bit in the wild, but since someone might not be aware of it, it might be worth sharing :)