Skip to content

Commit ba61dbc

Browse files
ematipicoArmandPhilippotyanthomasdev
authored
feat: astro logger (#13787)
Co-authored-by: Armand Philippot <git@armand.philippot.eu> Co-authored-by: Yan <61414485+yanthomasdev@users.noreply.github.com>
1 parent 9debc62 commit ba61dbc

2 files changed

Lines changed: 329 additions & 0 deletions

File tree

astro.sidebar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export const sidebar = [
158158
'reference/experimental-flags/svg-optimization',
159159
'reference/experimental-flags/queued-rendering',
160160
'reference/experimental-flags/rust-compiler',
161+
'reference/experimental-flags/logger',
161162
],
162163
}),
163164
'reference/legacy-flags',
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
---
2+
title: Experimental logger
3+
sidebar:
4+
label: Logger
5+
i18nReady: true
6+
---
7+
8+
import Since from '~/components/Since.astro';
9+
10+
<p>
11+
12+
**Type:** `object`<br />
13+
**Default:** `undefined`<br />
14+
<Since v="6.2.0" />
15+
</p>
16+
17+
Enables an experimental, custom logger that can be controlled by the user.
18+
19+
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`.
20+
21+
The following example enables the built-in JSON logger, with a pretty output:
22+
23+
```js title="astro.config.mjs" {5} ins="logHandlers"
24+
import { defineConfig, logHandlers } from 'astro/config';
25+
26+
export default defineConfig({
27+
experimental: {
28+
logger: logHandlers.json({ pretty: true })
29+
}
30+
});
31+
```
32+
33+
Alternatively, you can enable JSON logging for the `dev`, `build`, and `sync` commands using the `--experimentalJson` flag:
34+
35+
```shell
36+
astro dev --experimentalJson
37+
astro sync --experimentalJson
38+
astro build --experimentalJson
39+
```
40+
41+
42+
## Built-in loggers
43+
44+
The feature comes with three built-in loggers.
45+
46+
### `logHandlers.json`
47+
48+
A logger that outputs messages in a JSON format. A log would look like this:
49+
```json
50+
{ "message": "<the message>", "label": "router", "level": "info", "time": "<UNIX timestamp>" }
51+
```
52+
53+
#### JSON logger options
54+
55+
<p>
56+
**Type:** `{ pretty: boolean; level: AstroLoggerLevel; }`<br />
57+
**Default:** `{ pretty: false, level: 'info' }`
58+
<Since v="6.2.0" />
59+
</p>
60+
61+
The `json` logger accepts the following options:
62+
63+
- `pretty`: when `true`, the JSON log is printed on multiple lines. Defaults to `false`.
64+
- `level`: the [level](#log-level) of logs that should be printed.
65+
66+
```js title="astro.config.mjs" {5} ins="json"
67+
import { defineConfig, logHandlers } from 'astro/config';
68+
69+
export default defineConfig({
70+
experimental: {
71+
logger: logHandlers.json({ pretty: true })
72+
}
73+
});
74+
```
75+
76+
### `logHandlers.console`
77+
78+
A logger that prints messages using the `console` as its destination. Based on the level of the message, it uses different channels:
79+
80+
- `error` messages are printed using `console.error()`.
81+
- `warn` messages are printed using `console.warn()`.
82+
- `info` messages are printed using `console.info()`.
83+
84+
#### Console logger options
85+
86+
<p>
87+
**Type:** `{ level: AstroLoggerLevel }`<br />
88+
**Default:** `{ level: 'info' }`
89+
<Since v="6.2.0" />
90+
</p>
91+
92+
The `console` logger accepts the following options:
93+
94+
- `level`: the [level](#log-level) of logs that should be printed.
95+
96+
```js title="astro.config.mjs" {5} ins="console"
97+
import { defineConfig, logHandlers } from 'astro/config';
98+
99+
export default defineConfig({
100+
experimental: {
101+
logger: logHandlers.console({ level: 'warn' })
102+
}
103+
});
104+
```
105+
106+
### `logHandlers.node`
107+
108+
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`.
109+
110+
This is Astro's default logger.
111+
112+
#### Node logger options
113+
114+
<p>
115+
**Type:** `{ level: AstroLoggerLevel }`<br />
116+
**Default:** `{ level: 'info' }`
117+
<Since v="6.2.0" />
118+
</p>
119+
120+
The `node` logger accepts the following options:
121+
122+
- `level`: the [level](#log-level) of logs that should be printed.
123+
124+
```js title="astro.config.mjs" {5} ins="node"
125+
import { defineConfig, logHandlers } from 'astro/config';
126+
127+
export default defineConfig({
128+
experimental: {
129+
logger: logHandlers.node({ level: 'warn' })
130+
}
131+
});
132+
```
133+
134+
### `logHandlers.compose`
135+
136+
A particular function that allows configuring multiple loggers in an arbitrary order. The same message is broadcast to all loggers.
137+
138+
The following example composes the console logger and JSON logger using the default log level:
139+
140+
```js title="astro.config.mjs"
141+
import { defineConfig, logHandlers } from 'astro/config';
142+
143+
export default defineConfig({
144+
experimental: {
145+
logger: logHandlers.compose(
146+
logHandlers.console(),
147+
logHandlers.json()
148+
)
149+
}
150+
});
151+
```
152+
153+
## Custom loggers
154+
155+
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.
156+
157+
The logger function must be exported as `default`.
158+
159+
When you define a custom logger, you are in charge of all logs, even the ones emitted by Astro.
160+
161+
The following example defines a custom logger exported by the `@org/custom-logger` package and accepting only one parameter to configure the logging `level`:
162+
163+
```js title="astro.config.mjs"
164+
import { defineConfig } from 'astro/config';
165+
166+
export default defineConfig({
167+
experimental: {
168+
logger: {
169+
entrypoint: "@org/custom-logger",
170+
config: {
171+
level: "warn"
172+
}
173+
}
174+
}
175+
});
176+
```
177+
178+
The following example implements a minimal logger returning an [`AstroLoggerDestination` object](#astrologgerdestination) with the required `write()` function:
179+
180+
```ts title="@org/custom-logger/index.ts"
181+
import type {
182+
AstroLoggerLevel,
183+
AstroLoggerDestination,
184+
AstroLoggerMessage
185+
} from "astro";
186+
import { matchesLevel } from "astro/logger";
187+
188+
type LoggerOptions = {
189+
level: AstroLoggerLevel
190+
}
191+
192+
function orgLogger(options: LoggerOptions = {}): AstroLoggerDestination {
193+
const { level = 'info' } = options;
194+
return {
195+
write(message: AstroLoggerMessage) {
196+
// Use this utility to understand if the message should be printed
197+
if (matchesLevel(message.level, level)) {
198+
// log message somewhere and take the level into consideration
199+
}
200+
}
201+
}
202+
}
203+
204+
export default orgLogger;
205+
```
206+
207+
## Runtime
208+
209+
The [context object](/en/reference/api-reference/#the-context-object) now exposes a `logger` object containing the following functions:
210+
211+
- `error()`, which emits a message with `error` level.
212+
- `warn()`, which emits a message with `warn` level.
213+
- `info()`, which emits a message with `info` level.
214+
215+
```astro
216+
---
217+
Astro.logger.error("This is an error");
218+
Astro.logger.warn("This is a warning");
219+
Astro.logger.info("This is an info");
220+
---
221+
```
222+
223+
## Log level
224+
225+
A level is an internal, arbitrary score assigned to each message. When a logger is configured with a certain level, only the messages with a level equal to or higher are printed.
226+
227+
There are three levels, from the highest to the lowest score:
228+
1. `error`
229+
2. `warn`
230+
3. `info`
231+
232+
The following example configures the JSON logger to print only messages that have the `warn` level or higher:
233+
234+
```js title="astro.config.mjs" {5} ins='level: "warn"'
235+
import { defineConfig, logHandlers } from 'astro/config';
236+
237+
export default defineConfig({
238+
experimental: {
239+
logger: logHandlers.json({ level: "warn" })
240+
}
241+
});
242+
```
243+
244+
The `astro/logger` package exposes a [`matchesLevel()`](#matcheslevel) helper to check the log level. This can be useful when [building a custom logger](#custom-loggers).
245+
246+
```js
247+
import { matchesLevel } from "astro/logger";
248+
249+
matchesLevel("error", "info");
250+
```
251+
252+
253+
## Types reference
254+
255+
The following types can be imported from the `astro` specifier.
256+
257+
### `AstroLoggerDestination`
258+
259+
This is the interface that custom loggers must implement.
260+
261+
#### `AstroLoggerDestination.write()`
262+
263+
<p>
264+
265+
**Type:** `(message: AstroLoggerMessage) => void`
266+
</p>
267+
268+
A mandatory method called for each log and accepting an [`AstroLoggerMessage`](#astrologgermessage).
269+
270+
#### `AstroLoggerDestination.flush()`
271+
272+
<p>
273+
274+
**Type:** `() => Promise<void> | void`
275+
</p>
276+
277+
An optional function called at the end of each request. This is useful for advanced loggers that need to flush log messages while keeping the connection to the destination alive.
278+
279+
#### `AstroLoggerDestination.close()`
280+
281+
<p>
282+
283+
**Type:** `() => Promise<void> | void`
284+
</p>
285+
286+
An optional function called before a server is shut down. This function is usually called by adapters such as `@astrojs/node`.
287+
288+
### `AstroLoggerLevel`
289+
290+
The level of the message. Available variants:
291+
- `'debug'`
292+
- `'info'`
293+
- `'warn'`
294+
- `'error'`
295+
- `'silent'`
296+
297+
### `AstroLoggerMessage`
298+
299+
<p>
300+
301+
**Type:** `{ label: string | null; level: AstroLoggerLevel; message: string; newLine: boolean; }`
302+
</p>
303+
The incoming object from the [`AstroLoggerDestination.write()`](#astrologgerdestinationwrite) function:
304+
- `message`: the message being logged.
305+
- `level`: the level of the message.
306+
- `label`: an arbitrary label assigned to the log message.
307+
- `newLine`: whether this message should add a trailing newline.
308+
309+
## APIs reference
310+
311+
The following APIs can be imported from the `astro/logger` specifier.
312+
313+
### `matchesLevel`
314+
315+
<p>
316+
317+
**Type:** `matchesLevel(messageLevel: AstroLoggerLevel, configuredLevel: AstroLoggerLevel) => boolean`
318+
</p>
319+
320+
Given two [log levels](#log-level), it returns whether the first level matches the second level.
321+
322+
```js
323+
import { matchesLevel } from "astro/logger";
324+
325+
matchesLevel("error", "info"); // true
326+
matchesLevel("info", "error"); // false
327+
328+
```

0 commit comments

Comments
 (0)