Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .changeset/silver-berries-send.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
'astro': minor
---

Adds experimental support for configurable log handlers.

This experimental feature provides better control over Astro's logging infrastructure by allowing users to replace the default console output with custom logging implementations (e.g., structured JSON). This is particularly useful for users using on-demand rendering and wishing to connect their log aggregation services such as Kibana, Logstash, CloudWatch, Grafana, or Loki.

By default, Astro provides three built-in log handlers (`json`, `node` and `console`), but you can also create your own.
#### JSON logging

JSON logging can be enabled via the CLI for the `build`, `dev`, and `sync` commands using the `experimentalJson` flag:


```js
// astro.config.mjs
import { defineConfig, logHandlers } from "astro/config";

export default defineConfig({
experimental: {
logger: logHandlers.json({
pretty: true,
level: 'warn'
})
}
})
```

#### Custom logger

You can also create your own custom logger by implementing the correct interface:

```js
// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
experimental: {
logger: {
entrypoint: "@org/custom-logger"
}
}
})
```

```ts
// @org/custom-logger.js
import type { AstroLoggerDestination, AstroLoggerMessage } from "astro";
import { matchesLevel } from "astor/logger";

function customLogger(level = 'info'): AstroLoggerDestination {
return {
write(message: AstroLoggerMessage) {
if (matchesLevel(message.level, level)) {
// write message somewhere
}
}
}
}
export default customLogger
```
Comment thread
ematipico marked this conversation as resolved.

For more information on enabling and using this feature in your project, see the [Experimental Logger docs](https://docs.astro.build/en/reference/experimental-flags/logger/).

For a complete overview, and to give feedback on this experimental API, see the [Custom logger RFC](https://github.com/withastro/roadmap/blob/logger/proposals/0059-custom-logger.md).
1 change: 1 addition & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"./errors": "./dist/core/errors/userError.js",
"./middleware": "./dist/core/middleware/index.js",
"./virtual-modules/*": "./dist/virtual-modules/*",
"./logger": "./dist/core/logger/public.js",
"./logger/json": "./dist/core/logger/impls/json.js",
"./logger/node": "./dist/core/logger/impls/node.js",
"./logger/compose": "./dist/core/logger/impls/compose.js",
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/actions/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export type ActionAPIContext = Pick<
| 'session'
| 'cache'
| 'csp'
| 'logger'
>;

export type MaybePromise<T> = T | Promise<T>;
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ function createManifest(
onRequest: middleware ?? NOOP_MIDDLEWARE_FN,
};
}

const root = new URL(import.meta.url);
return {
rootDir: root,
Expand Down Expand Up @@ -181,6 +180,7 @@ function createManifest(
experimentalQueuedRendering: manifest?.experimentalQueuedRendering ?? {
enabled: false,
},
experimentalLogger: manifest?.experimentalLogger ?? undefined,
};
}

Expand Down Expand Up @@ -273,6 +273,7 @@ type AstroContainerManifest = Pick<
| 'assetsDir'
| 'image'
| 'experimentalQueuedRendering'
| 'experimentalLogger'
>;

type AstroContainerConstructor = {
Expand Down
3 changes: 3 additions & 0 deletions packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type { BaseSessionConfig, SessionDriverFactory } from '../session/types.j
import type { DevToolbarPlacement } from '../../types/public/toolbar.js';
import type { MiddlewareMode } from '../../types/public/integrations.js';
import type { BaseApp } from './base.js';
import type { LoggerHandlerConfig } from '../logger/config.js';

type ComponentPath = string;

Expand Down Expand Up @@ -158,6 +159,8 @@ export type SSRManifest = {
};
internalFetchHeaders?: Record<string, string>;
logLevel: AstroLoggerLevel;
// Configure that tells us how to load the logger
experimentalLogger: LoggerHandlerConfig | undefined;
};

export type SSRActions = {
Expand Down
10 changes: 4 additions & 6 deletions packages/astro/src/core/base-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { CompiledCacheRoute } from './cache/runtime/route-matching.js';
import type { SessionDriverFactory } from './session/types.js';
import { NodePool } from '../runtime/server/render/queue/pool.js';
import { HTMLStringCache } from '../runtime/server/html-string-cache.js';
import { loadLogger } from './logger/load.js';

/**
* The `Pipeline` represents the static parts of rendering that do not change between requests.
Expand All @@ -46,7 +47,7 @@ export abstract class Pipeline {
nodePool: NodePool | undefined;
htmlStringCache: HTMLStringCache | undefined;

readonly logger: AstroLogger;
logger: AstroLogger;
readonly manifest: SSRManifest;
/**
* "development" or "production" only
Expand Down Expand Up @@ -230,11 +231,8 @@ export abstract class Pipeline {
return this.logger;
}
this.resolvedLogger = true;
if (this.manifest.logger) {
const mod = await this.manifest.logger();
if (mod?.default) {
this.logger.setDestination(mod.default);
}
if (this.manifest.experimentalLogger) {
this.logger = await loadLogger(this.manifest.experimentalLogger);
}
return this.logger;
}
Expand Down
4 changes: 0 additions & 4 deletions packages/astro/src/core/build/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { pluginAnalyzer } from './plugin-analyzer.js';
import { pluginComponentEntry } from './plugin-component-entry.js';
import { pluginCSS } from './plugin-css.js';
import { pluginInternals } from './plugin-internals.js';
import { pluginLogger } from './plugin-logger.js';
import { pluginMiddleware } from './plugin-middleware.js';
import { pluginPrerender } from './plugin-prerender.js';
import { pluginScripts } from './plugin-scripts.js';
Expand All @@ -24,9 +23,6 @@ export function getAllBuildPlugins(
pluginComponentEntry(internals),
pluginAnalyzer(internals),
pluginInternals(options, internals),
options.settings.config.experimental.logger
? pluginLogger(options.settings.config.experimental.logger, options, internals)
: undefined,
pluginMiddleware(options, internals),
vitePluginActionsBuild(options, internals),
...pluginCSS(options, internals),
Expand Down
77 changes: 0 additions & 77 deletions packages/astro/src/core/build/plugins/plugin-logger.ts

This file was deleted.

6 changes: 6 additions & 0 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,11 @@ async function buildManifest(

const middlewareMode = resolveMiddlewareMode(opts.settings.adapter?.adapterFeatures);

let experimentalLogger = undefined;
if (settings.config.experimental.logger) {
experimentalLogger = settings.config.experimental.logger;
}

return {
rootDir: opts.settings.config.root.toString(),
cacheDir: opts.settings.config.cacheDir.toString(),
Expand Down Expand Up @@ -388,5 +393,6 @@ async function buildManifest(
internalFetchHeaders,
logLevel: settings.logLevel,
shouldInjectCspMetaTags: shouldTrackCspHashes(settings.config.security.csp),
experimentalLogger,
};
}
11 changes: 8 additions & 3 deletions packages/astro/src/core/logger/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ export interface AstroLoggerDestination<T = unknown> {
* It receives a message and writes it into a destination
*/
write: (chunk: T) => void;
// NOTE: will document once we actually use it
/**
* It dumps logs without closing the connection to the destination.
* Method that can be used by specialized loggers.
*/
flush?: () => Promise<void> | void;
// NOTE: will document once we actually use it
/**
* It dumps logs and closes the connection to the destination.
* Method that can be used by specialized loggers.
*/
close?: () => Promise<void> | void;
}

// NOTE: this is a public type

/**
* The level of logging. Priority is the following:
* 1. debug
Expand Down
62 changes: 58 additions & 4 deletions packages/astro/src/core/logger/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,78 @@
import type { LoggerHandlerConfig } from './config.js';
import type { JonsHandlerConfig } from './impls/json.js';
import type { JsonHandlerConfig } from './impls/json.js';
import type { NodeHandlerConfig } from './impls/node.js';
import type { ConsoleHandlerConfig } from './impls/console.js';

export const logHandlers = {
json(config?: JonsHandlerConfig): LoggerHandlerConfig {
/**
* It uses the built-in Astro JSON logger.
* @example
* ```js
* export default defineConfig({
* experimental: {
* logger: logHandlers.json({ pretty: true })
* }
* })
* ```
*/
json(config?: JsonHandlerConfig): LoggerHandlerConfig {
return {
entrypoint: 'astro/logger/json',
config,
};
},
node(): LoggerHandlerConfig {
/**
* It uses the built-in Astro Node.js logger.
*
* @example
* ```js
* export default defineConfig({
* experimental: {
* logger: logHandlers.node({ pretty: true })
* }
* })
* ```
*/
node(config?: NodeHandlerConfig): LoggerHandlerConfig {
return {
entrypoint: 'astro/logger/node',
config,
};
},
console(): LoggerHandlerConfig {
/**
* It uses the built-in Astro console logger.
*
* @example
* ```js
* export default defineConfig({
* experimental: {
* logger: logHandlers.console({ pretty: true })
* }
* })
* ```
*/
console(config?: ConsoleHandlerConfig): LoggerHandlerConfig {
return {
entrypoint: 'astro/logger/console',
config,
};
},

/**
* It allows composing different loggers
*
* @example
* ```js
* export default defineConfig({
* experimental: {
* logger: logHandlers.compose([
* logHandlers.console(),
* logHandlers.json(),
* ])
* }
* })
* ```
*/
compose(...loggers: LoggerHandlerConfig[]): LoggerHandlerConfig {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe neither the documentation nor the changeset mentions this feature. Maybe for the changeset it's not important (ie. we can focus on the most important parts) but we probably want to mention it in the docs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left it out in purpose here, I forgot to add to the docs, I will address this shortly

return {
entrypoint: 'astro/logger/compose',
Expand Down
6 changes: 2 additions & 4 deletions packages/astro/src/core/logger/impls/compose.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type { AstroLoggerDestination } from '../core.js';

export function compose(
...destinations: AstroLoggerDestination<unknown>[]
): AstroLoggerDestination<unknown> {
export default function compose(destinations: AstroLoggerDestination[]): AstroLoggerDestination {
return {
write(chunk: unknown) {
write(chunk) {
for (const logger of destinations) {
logger.write(chunk);
}
Expand Down
Loading
Loading