Mazzarolo MatteoMazzarolo Matteo

Conditional logging with query parameters

By Mazzarolo Matteo


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 :)