# Logging

Log from your handlers, subscribers, and plugins through the logger each of them carries. Covers the five levels, the label on every line, extra arguments, format specifiers, channels, and logging several lines at once.

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](/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](/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://docs.seedcord.org/packages/logger/latest/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://docs.seedcord.org/packages/logger/latest/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. If your attach key matches one of the names above, `attach` throws `CorePluginReservedChannel`.

The [dev terminal](/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://docs.seedcord.org/packages/logger/latest/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://docs.seedcord.org/packages/logger/latest/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://docs.seedcord.org/packages/logger/latest/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://docs.seedcord.org/packages/logger/latest/logger-utilities) lists all of them.
