Spraxium logoSpraxium

Transports and Configuration

Configure the logger with custom levels, Discord transports, and token masking. Learn how to send logs to Discord channels, use webhooks, customize embed templates, and add command logging.

Configuring the logger

Keep logger configuration in a dedicated config/logger.config.ts file and reference it from spraxium.config.ts. That keeps the root config readable and makes it obvious where log levels, masking, timestamps, and Discord forwarding are defined.

The framework applies this configuration during bootstrap, before your providers begin emitting logs. In practice, that means the first line your bot writes already uses the configured timestamp format, custom levels, and transports.

config/logger.config.ts
import type { SpraxiumLoggerConfig } from '@spraxium/logger';

export const loggerConfig: SpraxiumLoggerConfig = {
  levels: [
    { name: 'audit', color: 'blueBright' },
    { name: 'payment', color: 'greenBright' },
  ],
  maskTokens: true,
  discord: {
    type: 'channel',
    channelId: '1234567890',
    levels: ['error', 'warn', 'command'],
  },
};
spraxium.config.ts
import { defineConfig } from '@spraxium/core';
import { loggerConfig } from './config/logger.config';

export default defineConfig((_env) => ({
  logger: loggerConfig,
}));

The levels array registers custom log levels with their console colors. Those levels become available through Logger.extend and are understood by both built-in transports. The maskTokens flag controls the TokenMasker, which is enabled by default. Only turn it off in tightly controlled local debugging sessions.

FieldTypeDefaultPurpose
levelsCustomLogLevel[][]Custom log level names and their console colors.
maskTokensbooleantrueRedact Discord token patterns in all log messages.
discordDiscordTransportConfigundefined

Configuration for sending logs to Discord. Omit to disable.

commandLoggingbooleanfalseWhen enabled, registers a listener that logs every slash command execution at the command level. See Command logging below.
timestampFormatdefault | iso | time-only | custom formatterdefaultControls how the timestamp prefix is rendered in console output. See Timestamp format below.

Do not disable masking in shared environments

If logs can end up in CI output, shared terminals, or Discord channels, keep token masking enabled. It is much cheaper to reveal a value intentionally in a temporary local test than to clean up a leaked bot token.

Timestamp format

The timestampFormat option controls the prefix shown before every console log line. Pick one of the built-in presets if you want consistency with the rest of the framework, or provide a custom formatter when your bot feeds logs into an existing local standard.

ValueExample outputBest fit
default12/04/2026 - 14:32:05Good default for most bots and local development.
iso2026-04-12T14:32:05.000ZBetter for log shipping, parsing, and machine-readable pipelines.
time-only14:32:05Useful in short-running local sessions where the date adds noise.
custom formatterAny string you returnUse when you need a locale-specific or existing house format.
config/logger.config.ts
import type { SpraxiumLoggerConfig } from '@spraxium/logger';

export const loggerConfig: SpraxiumLoggerConfig = {
  // Use ISO 8601 timestamps for structured log pipelines
  timestampFormat: 'iso',

  // Or provide a custom format
  // timestampFormat: (d) => d.toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo' }),
};

The Discord transport

The Discord transport forwards selected log entries as embeds to Discord. It only sends levels listed in its own levels array, so you can keep high-volume operational logs in the terminal while forwarding warnings, errors, or command audits to a shared channel.

Two delivery modes are available. In channel mode, the transport uses the bot client itself to look up the channel and send embeds. In webhook mode, it sends HTTP requests to a webhook URL, which is useful when the target destination is operationally separate from the bot runtime.

ModeBest whenTradeoff
channelThe bot is already in the server and you want one less external secret to manage.Delivery depends on the client lifecycle and channel permissions.
webhookYou want isolated delivery credentials or need to log to a destination outside the bot's guild footprint.You now manage a webhook URL as an extra secret.
import type { SpraxiumLoggerConfig } from '@spraxium/logger';

export const loggerConfig: SpraxiumLoggerConfig = {
  discord: {
    type: 'channel',
    channelId: '1234567890123456789',
    levels: ['error', 'warn'],
  },
};

In channel mode, the transport queues messages that arrive before the client is ready. Once the client emits ready, the queue flushes automatically. The queue has a maximum size of 100 entries to avoid memory growth during long startup sequences.

The transport also deduplicates entries. If the same level, context, and message are sent within one second, the second send is suppressed. This protects operational channels from accidental duplicate bursts caused by retry paths or overlapping event handlers.

Custom embed templates

By default, Discord embeds use the level as the title, the message as the description, a matching color, and an ISO timestamp. Override this with a custom embed template when you want a more operational layout, for example including shard, guild, or metadata fields in predictable positions.

config/logger.config.ts
import type { SpraxiumLoggerConfig } from '@spraxium/logger';

export const loggerConfig: SpraxiumLoggerConfig = {
  discord: {
    type: 'channel',
    channelId: '1234567890123456789',
    levels: ['error', 'warn', 'command'],
    embed: {
      title: '{{level}} Log',
      description: "```\n{{message}}\n```",
      color: 0xff6b6b,
      timestamp: true,
      footer: { text: "Shard {{shard}} \u00b7 {{date}} {{time}}" },
      fields: [
        { name: 'Context', value: '{{context}}', inline: true },
        { name: 'Level', value: '{{level}}', inline: true },
      ],
    },
  },
};

The available placeholder variables are resolved from the log entry at send time. The shard variable shows the shard ID when the bot is sharded, or "N/A" in a single process. Metadata passed through log is also exposed as placeholders, which is what makes command and audit logs especially useful in Discord.

PlaceholderSource
levelUppercase level name (INFO, ERROR, etc.)
messageThe log message text
contextLogger context name, empty if not set
shardShard ID or "N/A"
timestampISO 8601 timestamp
dateFormatted as DD/MM/YYYY
timeFormatted as HH:MM:SS (24h)

Command logging

The CommandLogger service automatically logs every slash command execution. It binds to the client's interactionCreate event and captures the command name, user tag, user ID, guild name, guild ID, and channel ID. Each execution emits a log entry at the command level with structured metadata that you can reuse in embed templates.

The standard way to enable command logging is through spraxium.config.ts. Set commandLogging to true inside the logger block and the framework calls CommandLogger.bind automatically during client wiring. This is the preferred setup for most bots because it stays inside the normal framework lifecycle.

config/logger.config.ts
import type { SpraxiumLoggerConfig } from '@spraxium/logger';

export const loggerConfig: SpraxiumLoggerConfig = {
  maskTokens: true,
  commandLogging: true,
  discord: {
    type: 'channel',
    channelId: '1234567890123456789',
    levels: ['command'],
    embed: {
      title: 'Command Executed',
      description: '`/{{command}}` by **{{user}}**',
      timestamp: true,
      footer: { text: '{{guild}} ({{guildId}})' },
    },
  },
};

If you are using the logger outside the framework or need to bind command logging manually, call CommandLogger.bind directly with the Discord client after it is ready. The command, user, guild, and channel metadata then become available to your embed template.

src/main.ts
import { CommandLogger } from '@spraxium/logger';

// Call after the client is ready
CommandLogger.bind(client);

Custom transports

Create a custom transport when console output and Discord delivery are not enough, for example when writing to files, external observability stacks, or internal event buses. A transport implements LogTransport, exposes a name, and provides a log method that receives a LogEntry.

Transport failures are isolated from the rest of the app. If a transport throws or rejects, the logger swallows that failure so logging never becomes a single point of failure for your bot runtime.

src/transports/file.transport.ts
import { appendFileSync } from 'node:fs';
import type { LogEntry, LogTransport } from '@spraxium/logger';

export class FileTransport implements LogTransport {
  readonly name = 'file';

  constructor(private readonly path: string) {}

  log(entry: LogEntry): void {
    const line = `[${entry.timestamp.toISOString()}] ${entry.level.toUpperCase()} ${entry.context ?? ''} ${entry.message}\n`;
    appendFileSync(this.path, line);
  }
}

Register the transport with Logger.addTransport. If a transport with the same name already exists, it is replaced. You can remove a transport by name with Logger.removeTransport. The ConsoleTransport is always registered by default and uses the name "console".

src/main.ts
import { Logger } from '@spraxium/logger';
import { FileTransport } from './transports/file.transport';

Logger.addTransport(new FileTransport('./logs/bot.log'));

If your transport needs the Discord client (for example to look up channel names or resolve guild data), implement the ClientAwareTransport interface instead. It adds a setClient method that the framework calls automatically when the client is ready. The built-in DiscordTransport uses this interface internally.