diff --git a/astro.sidebar.ts b/astro.sidebar.ts index f2bcf97c84029..9cc4c15349db2 100644 --- a/astro.sidebar.ts +++ b/astro.sidebar.ts @@ -158,6 +158,7 @@ export const sidebar = [ 'reference/experimental-flags/svg-optimization', 'reference/experimental-flags/queued-rendering', 'reference/experimental-flags/rust-compiler', + 'reference/experimental-flags/logger', ], }), 'reference/legacy-flags', diff --git a/src/content/docs/en/reference/experimental-flags/logger.mdx b/src/content/docs/en/reference/experimental-flags/logger.mdx new file mode 100644 index 0000000000000..527f0703fc107 --- /dev/null +++ b/src/content/docs/en/reference/experimental-flags/logger.mdx @@ -0,0 +1,296 @@ +--- +title: Experimental logger +sidebar: + label: Logger +i18nReady: true +--- + +import Since from '~/components/Since.astro'; + +

+ +**Type:** `object`
+**Default:** `undefined`
+ +

+ +Enables an experimental, custom logger that can be controlled by the user. + +When provided, the user has total control over the logs emitted by Astro. Additionally, the feature comes with some built-in loggers that can be used via the new `logHandlers`. + +The following example enables the built-in JSON logger, with a pretty output: + +```js title="astro.config.mjs" {5} ins="logHandlers" +import { defineConfig, logHandlers } from 'astro/config'; + +export default defineConfig({ + experimental: { + logger: logHandlers.json({ pretty: true }) + } +}); +``` + +Alternatively, you can enable JSON logging the `dev`, `build` and `sync` commands using the `--experimentalJson` flag: + +```shell +astro dev --experimentalJson +astro sync --experimentalJson +astro build --experimentalJson +``` + + +## Built-in loggers + +The feature comes with three built-in loggers. + +### `logHandlers.json` + +A logger that outputs messages in a JSON format. A log would look like this: +```json +{ "message": "", "label": "router", "level": "info", "time": "" } +``` + +#### JSON logger options + +

+**Type:** `{ pretty: boolean; level: AstroLoggerLevel; }`
+**Default:** `{ pretty: false, level: 'info' }` + +

+ +The `json` logger accepts the following options: + +- `pretty`: when `true`, the JSON log is printed on multiple lines. Defaults to `false` +- `level`: the [level](#log-level) of logs that should be printed. + +```js title="astro.config.mjs" {5} ins="json" +import { defineConfig, logHandlers } from 'astro/config'; + +export default defineConfig({ + experimental: { + logger: logHandlers.json({ pretty: true }) + } +}); +``` + +### `logHandlers.console` + +A logger that prints messages using the `console` as its destination. Based on the level of the message, it uses different channels: +- `error` messages are printed using `console.error` +- `warn` messages are printed using `console.warn` +- `info` messages are printed using `console.info` + +#### Console logger options + +

+**Type:** `{ level: AstroLoggerLevel }`
+**Default:** `{ level: 'info' }` + +

+ +The `console` logger accepts the following options: + +- `level`: the [level](#log-level) of logs that should be printed. + +```js title="astro.config.mjs" {5} ins="console" +import { defineConfig, logHandlers } from 'astro/config'; + +export default defineConfig({ + experimental: { + logger: logHandlers.console({ level: 'warn' }) + } +}); +``` + +### `logHandlers.node` + +A logger that prints messages to [`process.stdout`](https://nodejs.org/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/api/process.html#processstderr). Level `error` messages are printed to `stderr`, while the others are printed to `stdout`. + +This is Astro's default logger. + +#### Node logger options + +

+**Type:** `{ level: AstroLoggerLevel }`
+**Default:** `{ level: 'info' }` + +

+ +The `node` logger accepts the following options: + +- `level`: the [level](#log-level) of logs that should be printed. + +```js title="astro.config.mjs" {5} ins="node" +import { defineConfig, logHandlers } from 'astro/config'; + +export default defineConfig({ + experimental: { + logger: logHandlers.node({ level: 'warn' }) + } +}); +``` + +### `logHandlers.compose` + +A particular function that allows using multiple loggers. The same message is broadcasted to all loggers. The function accepts an arbitrary number of logger configurations.logHandlers + +The following example composes the console logger and JSON logger using the default log level: + +```js title="astro.config.mjs" +import { defineConfig, logHandlers } from 'astro/config'; + +export default defineConfig({ + experimental: { + logger: logHandlers.compose( + logHandlers.console(), + logHandlers.json() + ) + } +}); +``` + +## Custom loggers + +You can create a custom logger by providing the correct configuration to the `logger` setting. It accepts an object with a mandatory `entrypoint`, the module where the logger is exported, and an optional configuration to pass to the logger. The configuration must be serializable. + +The logger function must be exported as `default`. + +When you define a custom logger, you are in charge of all logs, even the ones emitted by Astro. + +The following example defines a custom logger exported by the `@org/custom-logger` package and accepting only one parameter to configure the logging `level`: + +```js title="astro.config.mjs" +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + experimental: { + logger: { + entrypoint: "@org/custom-logger", + config: { + level: "warn" + } + } + } +}); +``` + +The following example implements a minimal logger returning an `AstroLoggerDestination` object with the required `write()` function: + +```ts title="@org/custom-logger/index.ts" +import type { + AstroLoggerLevel, + AstroLoggerDestination, + AstroLoggerMessage +} from "astro"; +import { matchesLevel } from "astro/logger"; + +type LoggerOptions = { + level: AstroLoggerLevel +} + +function orgLogger(options: LoggerOptions = {}): AstroLoggerDestination { + const { level = 'info' } = options; + return { + write(message: AstroLoggerMessage) { + // Use utility to understand if the message should be printed + if (matchesLevel(message.level, level)) { + // log message somewhere and take level into consideration + } + } + } +} + +export default orgLogger; +``` + +## Runtime + +The [context object](/en/reference/api-reference/#the-context-object) now exposes a `logger` object containing the following functions: + +- `error()`, which emits an message with `error` level. +- `warn()`, which emits an message with `warn` level. +- `info()`, which emits an message with `info` level. + +```astro +--- +Astro.logger.error("This is an error"); +Astro.logger.warn("This is a warning"); +Astro.logger.info("This is an info"); +--- +``` + +## Log level + +A level is an internal, arbitrary score, assigned to each message. When a logger is configured with a certain level, only the messages with equals level is equal or higher are printed. + +There are three levels, from the highest to the lowest score: +1. `error` +2. `warn` +3. `info` + +The following example configures the JSON logger to print only messages that have the `warn` level or higher: + +```js title="astro.config.mjs" {5} ins='level: "warn"' +import { defineConfig, logHandlers } from 'astro/config'; + +export default defineConfig({ + experimental: { + logger: logHandlers.json({ level: "warn" }) + } +}); +``` + +We will expose an utility form the `astro/logger` package to check the log level. + +```js +import { matchesLevel } from "astro/logger" + +matchesLevel("error", "info"); +``` + + +## Types reference + +The following types can be imported from the `astro` specifier. + +### `AstroLoggerDestination` + +This is the interface that custom loggers must implement. The following can be implemented: +- `write(message: AstroLoggerMessage) => void`: a mandatory method that accepts a `AstroLoggerMessage`. It's called for every log. +- `flush() => Promise | void`: an optional function that is called at end of each request. Useful for advanced loggers for flushing logger messages while keeping the connection to the destination alive. +- `close() => Promise | void`: an optional function that is called before a server is shut down. This is a function usually called by adapters such as `@astrojs/node`. + +### `AstroLoggerLevel` + +The level of the message. Available variants: +- `'debug'` +- `'info'` +- `'warn'` +- `'error'` +- `'silent'` + +### `AstroLoggerMessage` + +The incoming object from the `AstroLoggerDestination.write` function: +- `message: string`: the message being logged. +- `level`: the level of the message. +- `label`: an arbitrary label assigned to the log message. +- `newLine`: whether this message should add a trailing newline. + +## APIs reference + +The following APIs can me imported from the `astro/logger` specifier. + +### `matchesLevel` + +`matchesLevel(messageLevel: AstroLoggerLevel, configuredLevel: AstroLoggerLevel): boolean` + +Given two [logger level](#log-level), it returns whether the first level matches the second level. + +```js +import { matchesLevel } from "astro/logger" + +matchesLevel("error", "info"); // true +matchesLevel("info", "error"); // false + +```