> ## Documentation Index
> Fetch the complete documentation index at: https://stars-components.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# @wolfstar/plugin-logger

> Replace the HTTP Framework console logger with one that fans entries out to Console, Sentry, consola, evlog, or winston transports.

```bash npm theme={"system"}
npm install @wolfstar/plugin-logger
```

```bash pnpm theme={"system"}
pnpm add @wolfstar/plugin-logger
```

```bash yarn theme={"system"}
yarn add @wolfstar/plugin-logger
```

```bash bun theme={"system"}
bun add @wolfstar/plugin-logger
```

> [card] [npm](https://npmx.dev/package/@wolfstar/plugin-logger)

> [card] [Source](https://github.com/wolfstar-project/plugins/tree/main/packages/plugin-logger)

> [!TIP]
>
> See the [Plugins guide](https://stars-components.js.org/documentation/guide/plugins.md) for how plugins from wolfstar-project/plugins fit alongside the core framework.

## Description

`@wolfstar/plugin-logger` replaces the built-in console logger of [`@wolfstar/http-framework`](https://stars-components.js.org/documentation/packages/http-framework)
with one that fans every entry out to a list of transports. It implements the same `ILogger` contract, so code that
already writes through `container.logger` keeps working. It replaces
`@wolfstar/logger`, which has been removed.

Every logging backend is an optional peer dependency. Install only the ones you use:

```bash theme={"system"}
pnpm add consola   # for @wolfstar/plugin-logger/consola
pnpm add evlog     # for @wolfstar/plugin-logger/evlog
pnpm add winston   # for @wolfstar/plugin-logger/winston
```

`SentryTransport` takes the Sentry client you already use (`@sentry/node`, `@sentry/bun`, and so on) through its
constructor, so it needs no extra dependency.

## Usage

Import the `register` entry point before you create your `Client`:

```ts theme={"system"}
import '@wolfstar/plugin-logger/register';
import { Client, LogLevel } from '@wolfstar/http-framework';

const client = new Client({
	logger: { level: LogLevel.Debug }
});

container.logger.info('Ready');
```

Without further configuration the logger writes to `console`, like the framework's built-in one.

### Activating with `modules`

On framework 6.1 and later, list the `./module` entry in `modules` in `stars.config` instead of importing `register`.
It needs the optional [`@wolfstar/kit`](https://stars-components.js.org/documentation/packages/kit) peer dependency, at `^0.2.0 || ^0.3.0`.

```ts theme={"system"}
// stars.config.ts
import { defineConfig } from '@wolfstar/http-framework/config';
import '@wolfstar/plugin-logger/module';

export default defineConfig({
	modules: ['@wolfstar/plugin-logger/module'],
	logger: { level: 20 }
});
```

The module reads its options from the `logger` key of `stars.config`. The `[specifier, options]` tuple of `modules`
still works. Its options are merged over the `logger` key, so they win.

The optional [`@wolfstar/schema`](https://stars-components.js.org/documentation/packages/schema) peer dependency types the `logger` key in `defineConfig`.
TypeScript only loads that type once the module is imported, so import `@wolfstar/plugin-logger/module` in `stars.config.ts`.

Module options must be JSON-serialisable. Transports are objects, so set them through `ClientOptions.logger.transports`.
The one exception is evlog, which the module can set up for you through its [`evlog` option](#evlog-through-the-module).

Without the `stars` CLI, pass the `./plugin` [`definePlugin`](https://stars-components.js.org/documentation/packages/http-framework#plugins) factory to the
`plugins` client option:

```ts theme={"system"}
import loggerPlugin from '@wolfstar/plugin-logger/plugin';

const client = new Client({ plugins: [loggerPlugin({ level: LogLevel.Debug })] });
```

Use only one of the module, the `./plugin` factory, and `register`. Combining them installs the plugin's hooks twice.

## Transports

A transport is any object implementing `Transport`:

```ts theme={"system"}
interface Transport {
	readonly level?: LogLevel;
	log(payload: LogPayload): void | Promise<void>;
	close?(): void | Promise<void>;
}
```

A `LogPayload` carries the raw `values` the caller passed, plus three fields derived from them. Every transport reads
an entry the same way through these fields:

| Field | Content |
| - | - |
| `message` | Strings and other non-object values joined by spaces. Falls back to the error's message. |
| `error` | The first `Error` among the values. |
| `context` | The plain objects among the values, shallow-merged. |

```ts theme={"system"}
container.logger.error('Failed to charge', { orderId: 7 }, error);
// message: 'Failed to charge', context: { orderId: 7 }, error
```

The fields are computed the first time a transport reads them, then cached. To build a payload for a custom transport
or a test, use `createLogPayload()`:

```ts theme={"system"}
import { LogLevel } from '@wolfstar/http-framework';
import { createLogPayload } from '@wolfstar/plugin-logger';

const payload = createLogPayload(LogLevel.Error, ['Failed to charge', { orderId: 7 }, new Error('declined')]);
await myTransport.log(payload);
```

`level` filters on top of the logger's own level, so a Sentry sink can take only errors while the console keeps
everything:

```ts theme={"system"}
import '@wolfstar/plugin-logger/register';
import * as Sentry from '@sentry/node';
import { Client, LogLevel } from '@wolfstar/http-framework';
import { ConsoleTransport, SentryTransport } from '@wolfstar/plugin-logger';

const client = new Client({
	logger: {
		level: LogLevel.Debug,
		transports: [
			new ConsoleTransport(),
			new SentryTransport({ client: Sentry }) // defaults to LogLevel.Error
		]
	}
});
```

A transport that throws, or returns a rejecting promise, never interrupts the caller. The error is caught and
reported to `console.error`.

| Transport | Entry point | Peer dependency |
| - | - | - |
| `ConsoleTransport` | `.` | none |
| `SentryTransport` | `.` | none |

### Sentry

`SentryTransport` takes its Sentry client through the constructor, so the package has no runtime dependency on a
Sentry SDK. Pass the module namespace of the SDK you use. What Sentry receives depends on the entry's level:

| Option | Sends | Default |
| - | - | - |
| `level` | An issue, with the message and context in `extra`. | `LogLevel.Error` |
| `breadcrumbLevel` | A breadcrumb, for entries below `level`. | off |
| `logLevel` | A [Sentry Log](https://docs.sentry.io/product/explore/logs/). Requires `enableLogs: true` in `Sentry.init`. | off |
| `flushTimeout` | Milliseconds to wait for the client to flush on close. | `2000` |

```ts theme={"system"}
import * as Sentry from '@sentry/node';
import { LogLevel } from '@wolfstar/http-framework';
import { SentryTransport } from '@wolfstar/plugin-logger';

new SentryTransport({
	client: Sentry,
	level: LogLevel.Error, // issues
	breadcrumbLevel: LogLevel.Info, // context attached to the next issue
	logLevel: LogLevel.Info // structured logs, searchable in Sentry
});
```

When an entry has an `Error`, the transport reports it with `captureException` and keeps the entry's message and
context in `extra`. Breadcrumbs are off by default because Sentry's default console integration already records
them for `console.*`. Turning them on next to a `ConsoleTransport` duplicates them. Closing the logger flushes the
client, so the last `fatal` entry before an exit is delivered.

### Backend adapters

Each adapter wraps a third-party logger as a transport and lives behind its own subpath, so the dependency is only
resolved when you import it:

| Transport | Entry point | Peer dependency |
| - | - | - |
| `ConsolaTransport` | `./consola` | `consola` |
| `EvlogTransport` | `./evlog` | `evlog` ^2.30.0 |
| `WinstonTransport` | `./winston` | `winston` |

```ts theme={"system"}
import { consola } from 'consola';
import { ConsolaTransport } from '@wolfstar/plugin-logger/consola';

new ConsolaTransport({ instance: consola });
```

#### evlog

`EvlogTransport` hands each entry to [evlog](https://www.evlog.dev) as a structured event. The event holds `message`,
the context fields, `tag`, and the `error` with its `cause` chain. The transport does not format or deliver anything
itself. evlog owns the pipeline, so drains, enrichers, sampling, and redaction configured in `initLogger` apply to
your bot's logs. All six levels map to the evlog method of the same name.

```ts theme={"system"}
import { initLogger, log } from 'evlog';
import { createAxiomDrain } from 'evlog/axiom';
import { createDrainPipeline } from 'evlog/pipeline';
import { EvlogTransport } from '@wolfstar/plugin-logger/evlog';

const drain = createDrainPipeline()(createAxiomDrain());
initLogger({ env: { service: 'bot' }, drain });

new EvlogTransport({ instance: log, drain, tag: 'bot' });
```

Pass the `drain` pipeline so closing the logger flushes it. Otherwise, events still buffered are lost when the
process exits. evlog also ships a Sentry drain, which makes `SentryTransport` redundant when evlog is already your
sink.

#### winston

```ts theme={"system"}
import { createLogger, transports } from 'winston';
import { WinstonTransport } from '@wolfstar/plugin-logger/winston';

new WinstonTransport({
	instance: createLogger({ level: 'silly', transports: [new transports.File({ filename: 'bot.log' })] })
});
```

`WinstonTransport` writes the message and the context fields, and serialises an `Error` into an `error` field with `name`, `message`, and `stack`. winston's default `npm` levels have no `fatal`, so a `fatal`
entry is written as `error` with a `fatal: true` field. Closing the logger more than once is safe.

> [!NOTE]
>
> Create the winston logger with `level: 'silly'`. winston's own level (`info` by default) filters on top of the plugin's and silently drops lower
> entries.

## evlog plugin

The `@wolfstar/plugin-logger/evlog/plugin` entry sets up evlog for you. It runs evlog's `initLogger`, adds an
`EvlogTransport` to the logger, and emits one wide event per interaction. Use it when evlog is your logging backend
and you want per-interaction events without wiring `initLogger` yourself.

### evlog through the module

With the Stars module, set the `evlog` option. Use `evlog: true` for evlog's defaults, or write the options inline:

```ts theme={"system"}
// stars.config.ts
export default defineConfig({
	modules: [
		[
			'@wolfstar/plugin-logger/module',
			{
				level: 20,
				evlog: {
					env: { service: 'bot' },
					sampling: { rates: { debug: 10 } },
					redact: true,
					pipeline: { batch: { size: 25 } },
					drain: './src/evlog-drain.ts'
				}
			}
		]
	]
});
```

```ts theme={"system"}
// src/evlog-drain.ts
import { createAxiomDrain } from 'evlog/axiom';
import { defineEvlogDrain } from '@wolfstar/plugin-logger/evlog/plugin';

export default defineEvlogDrain(createAxiomDrain());
```

| Option | Description |
| - | - |
| `drain` | File that default-exports `defineEvlogDrain(...)`. Accepts a path (relative to the project root when it starts with `.`), a `file:` URL, a package specifier, or `{ from, export }` for a named export. |
| `pipeline` | Wraps the drain in evlog's drain pipeline (batching, retry, bounded buffer). `true` for its defaults, or its options. The drain then receives events in batches. |
| `interactions` | Controls the [per-interaction wide events](#wide-events-per-interaction). Defaults to `true`. |
| `tag` | Tag every entry is written under. Defaults to `'http-framework'`. |
| `level` | Lowest level the `EvlogTransport` accepts. Defaults to the logger's level. |

Every other option (`env`, `pretty`, `silent`, `minLevel`, `sampling`, `redact`, and so on) goes to evlog's
`initLogger`. The options must be JSON-serialisable, since they are written into the built entry. The drain is a
function, so it comes from a file that Stars bundles with your bot. `evlog: true`, or options without `drain`, need
no file.

A drain that already has a `flush` method is used as is. Either way, closing the logger flushes it. evlog prints to
the console itself, so the module adds no `ConsoleTransport` next to it. Set `silent: true` when the drain should be
the only output.

Without the `stars` CLI, pass the drain to the `evlogPlugin` factory directly, before the logger plugin:

```ts theme={"system"}
import { createAxiomDrain } from 'evlog/axiom';
import evlogPlugin from '@wolfstar/plugin-logger/evlog/plugin';
import loggerPlugin from '@wolfstar/plugin-logger/plugin';

const client = new Client({
	plugins: [evlogPlugin({ env: { service: 'bot' }, pipeline: true, drain: createAxiomDrain() }), loggerPlugin()]
});
```

### Wide events per interaction

The evlog plugin follows the client's interaction lifecycle and drains one wide event per interaction. The event is
created when a command, component, or modal starts, filled with its outcome, and emitted when it finishes. It goes
through the same drain, sampling, redaction, and plugins as every other evlog event.

| Field | Content |
| - | - |
| `method` | `COMMAND`, `AUTOCOMPLETE`, `COMPONENT`, or `MODAL`. |
| `path` | The command or handler name. |
| `requestId` | The interaction ID. |
| `outcome` | How the interaction finished. |
| `error` | The error, when the interaction failed. |
| `durationMs` | How long the interaction took. |
| `guildId`, `channelId`, `userId`, `locale` | Where the interaction came from and who sent it. |

`interactions` accepts `true`, `false`, or an object with `commands`, `handlers`, and `autocomplete` flags. Commands
and handlers are on by default. Autocomplete is off by default, since Discord sends a request for every keystroke.

```ts theme={"system"}
// Add autocomplete
const options = { evlog: { interactions: { autocomplete: true } } };
// Turn the wide events off and keep only the container.logger entries
const quiet = { evlog: { interactions: false } };
```

From a command, autocomplete, or handler, `useInteractionLogger()` returns the interaction's evlog request logger so
you can add fields to its wide event. It throws when called outside an interaction.

```ts theme={"system"}
import { useInteractionLogger } from '@wolfstar/plugin-logger/evlog/plugin';

useInteractionLogger().set({ cart: { items: 3 } });
```

## Upgrading the transports

The transport rework changes what some backends receive and which peer dependencies the package declares:

* **Custom transports**: `LogPayload` now has `message`, `error`, and `context`. Read those instead of parsing
  `values`, and build test payloads with `createLogPayload()`.
* **Sentry**: the `@sentry/node` peer dependency is gone. Keep installing the Sentry SDK you pass as `client`.
  Issues created from an `Error` now carry the message and context in `extra` instead of the raw `values`.
* **evlog**: the `evlog` peer requires `^2.30.0`. Entries are structured events instead of flattened strings, and
  `trace` and `fatal` no longer collapse into `debug` and `error`. Update queries or dashboards that relied on the
  old string output.
* **winston**: entries now spread the context fields at the top level instead of a `values` array. An `Error` is
  written as an `error` field with `name`, `message`, and `stack` (JSON used to turn it into `{}`). `fatal` entries
  are still written as `error`, now with a `fatal: true` field.

## Migration from @wolfstar/logger

```diff theme={"system"}
-import { Logger, LogLevel } from '@wolfstar/logger';
+import '@wolfstar/plugin-logger/register';
+import { LogLevel, container } from '@wolfstar/http-framework';

-const logger = new Logger({ level: LogLevel.Debug });
-logger.info('Ready');
+const client = new Client({ logger: { level: LogLevel.Debug } });
+container.logger.info('Ready');
```

The `trace`, `debug`, `info`, `warn`, `error`, and `fatal` methods behave the same. `LogLevel` keeps its ordering and is
now imported from `@wolfstar/http-framework`.

[![version](https://npmx.dev/api/registry/badge/version/@wolfstar/plugin-logger)](https://npmx.dev/package/@wolfstar/plugin-logger)
[![downloads](https://npmx.dev/api/registry/badge/downloads/@wolfstar/plugin-logger)](https://npmx.dev/package/@wolfstar/plugin-logger)
[![license](https://img.shields.io/github/license/wolfstar-project/plugins?style=flat-square\&color=informational)](https://github.com/wolfstar-project/plugins/blob/main/LICENSE)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.