diff --git a/docs/content/3.middleware/2.guides/10.working-with-logger.md b/docs/content/3.middleware/2.guides/10.working-with-logger.md new file mode 100644 index 0000000000..6c996e52cd --- /dev/null +++ b/docs/content/3.middleware/2.guides/10.working-with-logger.md @@ -0,0 +1,336 @@ +# Working with 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. + +## 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. Whether you’re working in an extension or an integration, you can easily utilize the logger to capture and track necessary information. + +```json[Example log] +{ + "message": "log message we print", + "timestamp": "2024-10-22T19:04:18.791Z", + "severity": "INFO", + "context": "middleware", + "scope": { + "integrationName": "adyen", + "functionName": "addedCustomEndpoint", + "hookName": "afterCall", + "type": "requestHook", + "extensionName": "testing-adyen-extension" + } +} +``` + +In this section, we will explore how to access and use the logger in different parts of the application, ensuring you have the tools to log data efficiently and consistently. + +These examples will demonstrate the proper ways to obtain and interact with the logger, ensuring that you are able to implement consistent and effective logging practices across your middleware application. As you will see, `getLogger` function solves the problem in most cases. + +### Example log + +### extendApp + +```ts +import { getLogger } from "@vue-storefront/middleware"; + +const someExtension = { + name: "some-extension", + extendApp(params) { + const logger = getLogger(params); + logger.info("Logged succesfully!"); + } +}; +``` + +### hooks + +Logger is available in hooks factory: + +```ts +import { getLogger } from "@vue-storefront/middleware"; + +const someExtension = { + name: "some-extension", + hooks(req, res, alokai) { + const logger = getLogger(alokai); + logger.info("hooks"); + return { + // ... + }; + }, +}; +``` + +In order, to use logger in certain hooks like `beforeCall`, `beforeCreate`, `afterCall`, `afterCreate` obtain it from it's params: + +```ts +import { getLogger } from "@vue-storefront/middleware"; + +const someExtension = { + name: "some-extension", + hooks(req, res, alokai) { + return { + beforeCall(params) { + const logger = getLogger(params); + logger.info("smth"); + return params.args; + }, + beforeCreate(params) { + const logger = getLogger(params); + logger.info("smth"); + return params; + }, + afterCall(params) { + const logger = getLogger(params); + logger.info("smth"); + return params.response; + }, + afterCreate(params) { + const logger = getLogger(params); + logger.info("smth"); + return params; + }, + }; + }, +}; +``` + +#### Caveat: Accessing logger via closure + +Consider the following snippet: + +```ts +import { getLogger } from "@vue-storefront/middleware"; + +const someExtension = { + name: "some-extension", + hooks(req, res, alokai) { + const hooksLogger = getLogger(alokai); + hooksLogger.info("hooks"); + return { + beforeCall(params) { + // Never access via closure a logger belonging to the hooks factory: + hooksLogger.info("smth"); + // Instead use own logger of every hook function: + const logger = getLogger(params); + logger.info("smth"); + return params.args; + } + }; + }, +}; +``` + +:::warning +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 - available via `getLogger` function. +::: + +### extendApiMethods + +Inside extended api methods, obtain logger from `context` using `getLogger` function. + +```ts +import { getLogger } from "@vue-storefront/middleware"; + +const testingExtension = { + name: "some-extension", + extendApiMethods: { + addedCustomEndpoint(context) { + const logger = getLogger(context); + logger.info("some log"); + return { + // ... + }; + } + } +}; +``` + +:::tip +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. + +```ts +const onCreate = async ( + config: Record = {}, + alokai: AlokaiContainer +) => { + const logger = getLogger(alokai); + logger.info("smth"); + + return { + // ... + }; +}; +``` + +### init + +The hook got a new parameter from which you can derive the logger with help of `getLogger` function. + +```ts +const init = (settings: any, alokai: AlokaiContainer) => { + const logger = getLogger(alokai); + logger.info("smth"); + + return { + config: settings, + client: null, + }; +}; +``` + +## 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 + +```ts +/** + * Options for the logger. + */ +export interface LoggerOptions { + /** + * The log level aligned with RFC5424. + */ + level?: LogLevel; // (def. "info") + + /** + * Whether to include the stack trace in the log message. + */ + includeStackTrace?: boolean; // (def. true) + + /** + * Own implementation of logger to be used internally. + * + * @remarks If provided then other options won't be used. + */ + handler?: LoggerInterface; +} +``` + +#### 1. level + +This option sets the log level based on the [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424) 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 level to `error` ensures only error messages and above (i.e., critical, alert, emergency) will be captured: + +```json +{ + "logger": { + "level": "error" + } +} +``` + +For a development environment, you might prefer a more verbose log level like `debug`: + +```json +{ + "logger": { + "level": "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. + +```json +{ + "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`: + +```js[middleware.config.js] +{ + "logger": { + "includeStackTrace": true, + "level": "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`: + +```js +{ + "integrations": { + "commerce": { + "location": "@vsf-enterprise/sapcc-api/server", + + "configuration": { + // Configuration of integration itself + } + + // Configuration of the logger only for "commerce" integration + "logger": { + "includeStackTrace": true, + "level": "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. + +## Providing own Logger handler + +In middleware, you have the flexibility to override the default logger by providing your own custom logger handler. This can be done globally or on a per-integration basis, allowing for full customization of how logs are handled within the application. + +To override the logger, you need to provide an object in the configuration under the handler key. This object must conform to the following interface: + +```ts +/** + * Common interface for a logger. + */ +export interface LoggerInterface { + emergency(logData: LogData, metadata?: Metadata): void; + alert(logData: LogData, metadata?: Metadata): void; + critical(logData: LogData, metadata?: Metadata): void; + error(logData: LogData, metadata?: Metadata): void; + warning(logData: LogData, metadata?: Metadata): void; + notice(logData: LogData, metadata?: Metadata): void; + info(logData: LogData, metadata?: Metadata): void; + debug(logData: LogData, metadata?: Metadata): void; +} +``` + +Each method in this interface corresponds to a log level defined by [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424), and the logger implementation should handle the provided `logData` and optional `metadata`. + +Once a custom `handler` is provided, **all other logger configuration options (such as `level` or `includeStackTrace`) will no longer be used**. This allows for complete control over how the logs are processed. diff --git a/packages/middleware/src/types/config.ts b/packages/middleware/src/types/config.ts index d1fd71edc3..3df4e0ce39 100644 --- a/packages/middleware/src/types/config.ts +++ b/packages/middleware/src/types/config.ts @@ -38,7 +38,7 @@ export interface LoggerOptions { * * @remarks If provided then other options won't be used. */ - handler?: LoggerInterface; // Should it have .log? + handler?: LoggerInterface; } export interface Helmet extends HelmetOptions {