Vue Storefront is now Alokai! Learn More
Logger

Logger

The middleware application provides a logger instance that automatically attaches metadata related to the scope of each call. By adding this contextual data, the logger makes it significantly easier to trace the origin of logs or errors, simplifying the debugging and monitoring process across the application.

The logger has been available since version 5.1.0. Please refer to the middleware changelog for more details.

Using logger

The middleware application provides access to the logger in various parts of the system, such as in extensions and integrations. This flexibility allows you to log important events, errors, and other data throughout the lifecycle of your application.

In this section, we will explore how to access and use the logger in different parts of the application.

The examples below demonstrate how to use the application logger to ensure that correct metadata is conveyed in the logs. To achieve that, we will use the getLogger function to extract the logger instance from the passed argument.

The logger is integrated into the middleware to provide additional metadata about the scope of call with every log out of the box. In case of error, it also provides an error boundary.

Basic Logger usage

To use the logger, you need to import the getLogger function from the middleware package. Depending on the context, you can obtain the logger from different sources, such as the alokai object, context object, or params object. All such cases are covered in the following examples. For this example, we will use the context object.

import { getLogger } from "@vue-storefront/middleware";

// Assume that `context` is available in the current scope
const logger = getLogger(context);
logger.info("Middleware is running!");

Prints:

{
  "message": "Middleware is running!",
  "timestamp": "2024-10-22T19:04:18.791Z",
  "severity": "INFO",
  "alokai": {
    "context": "middleware",
    "scope": {
      // ...
    }
  }
}

The provided logger has a second optional argument that allows you to include custom metadata to be attached to the JSON output. For example:

logger.info("I've just fetched a user!", {
  userId: "123",
});

Prints:

{
  "message": "I've just fetched a user!",
  "timestamp": "2024-10-22T19:04:18.791Z",
  "severity": "INFO",
  "alokai": {
    "context": "middleware",
    "scope": {
      // ...
    }
  },
  "userId": "123"
}

It's forbidden to overwrite alokai key within provided metadata. Any attempt will be ineffective. If you need to provide additional metadata, use a different key. alokai key is reserved for internal use only.

extendApp

import { getLogger } from "@vue-storefront/middleware";

const someExtension = {
  name: "some-extension",
  extendApp(params) {
    const logger = getLogger(params);

    logger.info(
      "You can call a logger inside extendApp hook, called on bootstrap of the application"
    );
  },
};

Hooks

Logger is available in hooks factory:

import { getLogger } from "@vue-storefront/middleware";

const paypalExtension = {
  name: "sapcc-paypal-extension",
  hooks(req, res, alokai) {
    const logger = getLogger(alokai);
    logger.info(
      "You can call a logger every time a hooks factory gets invoked"
    );
    return {
      // ...
    };
  },
};

In order, to use logger in certain hooks like beforeCall, beforeCreate, afterCall, afterCreate obtain it from it's params:

import { getLogger } from "@vue-storefront/middleware";

const paypalExtension = {
  name: "sapcc-paypal-extension",
  hooks(req, res, alokai) {
    return {
      beforeCall(params) {
        const logger = getLogger(params);
        logger.info("You can log every time a beforeCall hook gets invoked");
        return params.args;
      },
      beforeCreate(params) {
        const logger = getLogger(params);
        logger.info("You can log every time a beforeCreate hook gets invoked");
        return params.configuration;
      },
      afterCall(params) {
        const logger = getLogger(params);
        logger.info("You can log every time a afterCall hook gets invoked");
        return params.response;
      },
      afterCreate(params) {
        const logger = getLogger(params);
        logger.info("You can log every time a afterCreate hook gets invoked");
        return params.configuration;
      },
    };
  },
};

Caveat: Accessing logger via closure

Consider the following snippet:

import { getLogger } from "@vue-storefront/middleware";

const someExtension = {
  name: "some-extension",
  hooks(req, res, alokai) {
    const hooksLogger = getLogger(alokai);
    hooksLogger.info(
      "You can call a logger every time a hooks factory gets invoked"
    );
    return {
      beforeCall(params) {
        // Never access via closure a logger belonging to the hooks factory:
        hooksLogger.info("Don't do that!");
        // Instead use own logger of every hook function:
        const logger = getLogger(params);
        logger.info("Do that!");
        return params.args;
      },
    };
  },
};

Attempt of using hooksLogger inside beforeCall or other hooks via closure corrupts information about scope of call of the log. Always use logger provided in params to the hook.

extendApiMethods

Inside extended api methods, obtain logger from context using getLogger function.

import { getLogger } from "@vue-storefront/middleware";

const testingExtension = {
  name: "some-extension",
  extendApiMethods: {
    addedCustomEndpoint(context) {
      const logger = getLogger(context);
      logger.info("You can log inside newly created handler!");
      return {
        // ...
      };
    },
  },
};

You can derive logger from context the same way if you are creating an integration.

onCreate

The hook got a new parameter from which you can derive the logger with help of getLogger function.

import { type AlokaiContainer, getLogger } from "@vue-storefront/middleware";

const onCreate = async (
  config: Record<string, unknown> = {},
  alokai: AlokaiContainer
) => {
  const logger = getLogger(alokai);
  logger.info("You can log inside onCreate when creating a new integration");

  return {
    // ...
  };
};

Configuration

The middleware logger offers flexible configuration options to control logging behavior across the application. These options allow you to manage the verbosity and level of detail in logs based on the needs of different environments, such as development or production.

Available Configuration Options

/**
 * Options for the logger.
 */
export interface LoggerOptions {
  /**
   * The log level aligned with RFC5424.
   */
  verbosity?: LogVerbosity; // (def. "info")

  /**
   * Whether to include the stack trace in the log message.
   */
  includeStackTrace?: boolean; // (def. true)
}

1. Verbosity

This option sets the log verbosity level based on the RFC 5424 syslog standard. It defines the severity of log messages that should be recorded. Available log levels are:

  • emergency: The highest severity level, indicating a system-wide emergency.
  • alert: Indicates an urgent condition that requires immediate attention.
  • critical: Highlights critical issues that need immediate action.
  • error: Used to log error messages that could affect functionality.
  • warning: Logs warnings about potential issues that should be monitored.
  • notice: Records significant but non-critical events.
  • info: Logs general informational messages indicating normal operation.
  • debug: The most detailed log level, useful for tracing and debugging the application.

Example: Setting the log verbosity level to error ensures only error messages and above (i.e., critical, alert, emergency) will be captured:

{
  "logger": {
    "verbosity": "error"
  }
}

For a development environment, you might prefer a more verbose log level like debug:

{
  "logger": {
    "verbosity": "debug"
  }
}

2. includeStackTrace

This option specifies whether the stack trace should be included in the log messages. If set to true, stack traces will be included in error messages, which can be helpful during debugging. If set to false, logs will be more concise.

{
  "logger": {
    "includeStackTrace": true
  }
}

Global configuration

To configure the logger globally, add a logger object to the top-level configuration of the middleware. This configuration will apply to all integrations and parts of the application unless specifically overridden. Here’s an example of a global configuration where the stack trace is included in the logs, and the log level is set to debug:

middleware.config.js
export const config = {
  "logger": {
    "includeStackTrace": true,
    "verbosity": "debug"
  },
  // The rest of the middleware configuration
  "integrations": {
    // ...
  }
}

This global logger configuration ensures that all logs, regardless of the integration or scope, will follow these settings.

Per integration configuration

If you need different logging settings for specific integrations, you can configure the logger separately within each integration. The logger configuration should be added at the same level as location and configuration keys within the integration’s object.

For example, here is how you would configure the logger for an integration with the key commerce:

middleware.config.js
export const config = {
  "integrations": {
    "commerce": {
      "location": "@vsf-enterprise/sapcc-api/server",

      "configuration": {
        // Configuration of integration itself
      }

      // Configuration of the logger only for "commerce" integration
      "logger": {
        "includeStackTrace": true,
        "verbosity": "debug"
      },
    }
  }
}

In this case, the logging configuration applies only to the commerce integration, overriding the global settings if they exist. The logger configuration can vary for each integration, allowing you to customize the level of logging detail based on the needs of specific integrations.

This approach provides the flexibility to define global logging behavior while also giving control to fine-tune logging per integration, ensuring you capture the necessary details for debugging and monitoring in the most relevant areas.