# Logging

Log from your handlers, subscribers, and plugins through the logger each one carries, with five levels and named channels.

A `console.log` in a handler prints a bare string. You can't tell which handler wrote it, you can't hide it in production without deleting it, and the [dev terminal](https://seedcord.org/guide/tooling/dev/) can't filter it. Every handler, subscriber, and plugin carries a `logger` that fixes all three, and you call it with one of five methods.

```ts title="src/handlers/Ping.ts"
import { SlashHandler, SlashRoute } from '@seedcord/gateway';

@SlashRoute('ping')
export class Ping extends SlashHandler<'ping'> {
    public async execute(): Promise<void> {
        this.logger.info('checking latency');
        await this.reply('Pong');
    }
}
```

`Ping` logs `checking latency` at the `info` level before it replies.

## The five levels

A level marks how much a line matters, so you can keep detailed logging in your code and print only part of it in production.

{/* prettier-ignore-start */}

| method  | what it marks                                       |
| ------- | --------------------------------------------------- |
| `error` | something broke and you want to know about it       |
| `warn`  | something looks wrong, though your bot kept running |
| `info`  | the ordinary record of what your bot did            |
| `debug` | detail you want while you debug something           |
| `trace` | the noisiest detail, usually one step inside a loop |

{/* prettier-ignore-end */}

The level you configure is a floor. seedcord prints that level and every more serious one. Your environment sets the default: `trace` in development, so every line prints, `debug` in staging, and `info` everywhere else, which hides `debug` and `trace`. [Configuring the logger](https://seedcord.org/guide/tooling/logger-config/) shows how to change it.

## Every line carries your class name

When two handlers log the same message, you tell them apart by the label. A logger takes its label from the class holding it, so a line from `Ping` shows `Ping` as its label.

## More than a string

A message alone often omits the one value you needed, like which user ran the command. Every method takes extra arguments after the message.

```ts
this.logger.debug(
    '%s ran %s',
    this.event.user.username,
    this.event.commandName
);

this.logger.info('replying', {
    userId: this.event.user.id
});

try {
    await this.reply('Pong');
} catch (error) {
    this.logger.error('reply failed', error);
}
```

Those three calls print this in the dev terminal.

```txt output
materwelon ran ping
replying
{
  "userId": "1176558419439161344"
}
reply failed
TypeError: Cannot read properties of undefined
    at Ping.execute (src/handlers/Ping.ts:14:19)
```

An object prints as a block under the message, and the caught `error` prints its stack there. A string or a number prints on the same line, after the message. A JSON sink writes one *record* per call, a single object. Every one of those arguments arrives in an `extras` array on it, and the first `Error` fills its `stack` as well.

### Format specifiers

The message itself takes format specifiers, like the two `%s` in the `debug` call. Each one consumes the next argument.

{/* prettier-ignore-start */}

| specifier     | the argument   | what prints  |
| ------------- | -------------- | ------------ |
| `%s`          | `'materwelon'` | `materwelon` |
| `%d` and `%i` | `3.7`          | `3`          |
| `%f`          | `3.7`          | `3.7`        |
| `%j`          | `{ id: 4 }`    | `{"id":4}`   |
| `%o` and `%O` | `{ id: 4 }`    | `{ id: 4 }`  |
| `%%`          | none           | `%`          |

{/* prettier-ignore-end */}

`%o` and `%O` print an object the way `console.log` does. [`ObjectConsoleSink`](https://seedcord.org/docs/packages/logger/latest/classes/object-console-sink), which an edge bot runs, prints `{"id":4}` for `%o` and `%O` too. Any argument the specifiers don't consume is treated like the extra arguments above.

## Channels

A bot writes lines about commands, events, plugins, and startup all at once, and you usually care about one of those at a time. Every line also carries a channel, which is a label for the part of your bot the line came from. seedcord picks the channel for its own classes. For example, an interaction handler logs on `interactions`, an event handler on `events`, and a subscriber on `subscribers`.

These names belong to the framework, and [`FRAMEWORK_CHANNELS`](https://seedcord.org/docs/packages/logger/latest/variables/framework-channels) holds every one of them.

{/* prettier-ignore-start */}

| channel        | what arrives there                                                                             |
| -------------- | ---------------------------------------------------------------------------------------------- |
| `default`      | a logger built without a channel                                                               |
| `bot`          | the client login on gateway, the interactions server on http, plus emoji and command injection |
| `lifecycle`    | each startup and shutdown task, and the signal that stopped your bot                           |
| `health`       | the health check server binding and its errors                                                 |
| `interactions` | which handler ran, unmatched routes, and how long dispatch and the reply took                  |
| `events`       | which listeners attached, and which handler ran for an event. Gateway only                     |
| `commands`     | the commands seedcord loaded, and the deploy summary                                           |
| `subscribers`  | bus activity, unreachable webhooks, and errors thrown inside a subscriber                      |
| `errors`       | uncaught process errors and fault reports. An http bot reports faults on `interactions`        |
| `gates`        | a warning when your gate checks use too much of Discord's 3s window                            |
| `plugins`      | how long each plugin took to start                                                             |
| `hmr`          | what reloaded while `seedcord dev` runs                                                        |
| `cli`          | status and errors from the CLI, codegen and the tunnel included                                |
| `tsc`          | output from `tsc --watch`, once you turn `hmr.typecheck` on                                    |

{/* prettier-ignore-end */}

Your own bot adds channels to that set. A plugin logs on a channel named after the attach key you chose, so attaching one as `db` sends its lines to the `db` channel, and one as `services.users` to `services.users`. If your attach key matches one of the names above, or the group in front of your dot does, `attach` throws `CorePluginReservedChannel`.

The [dev terminal](https://seedcord.org/guide/tooling/dev/) draws every channel as a filter chip, so the more you split your bot into channels, the more precisely you can filter it.

### Sending one line somewhere else

A handler that charges a user belongs on `interactions`, but you'd want its payment lines next to the rest of your payment logs. [`inChannel`](https://seedcord.org/docs/packages/logger/latest/classes/logger#in-channel) returns a second logger with the same label, on the channel you pass.

```ts
this.logger.inChannel('payments').info('charge accepted');
```

That line shows `Ping` as its label and arrives on `payments`. Name your own channels after the parts of your bot you'll want to filter. [`setChannel`](https://seedcord.org/docs/packages/logger/latest/classes/logger#set-channel) moves the logger you already have to a new channel for every later line.

## Several lines at once

Logging a list one call at a time gives the dev terminal several records, and output from elsewhere in your bot can appear between them. `block` on [`logger.utils`](https://seedcord.org/docs/packages/logger/latest/classes/logger#utils) writes the whole group as a single record.

```ts
this.logger.utils.block(
    'Cache warmed',
    ['guilds 41', 'members 1204'],
    'info'
);
```

`block` takes a heading (`Cache warmed` here), its lines, and a level. The level defaults to `trace`, which only prints in development, so the sample passes `'info'`. The other helpers on `logger.utils` default to `info`, and `list` writes one record per line, so it gets the interleaving `block` avoids. [`LoggerUtilities`](https://seedcord.org/docs/packages/logger/latest/classes/logger-utilities) lists all of them.
