Configurando o logger
Mantenha a configuração do logger em um arquivo dedicado, como config/logger.config.ts, e referencie esse arquivo a partir de spraxium.config.ts. Isso deixa a configuração raiz legível e deixa claro onde níveis, masking, timestamps e encaminhamento para o Discord estão definidos.
O framework aplica essa configuração durante o bootstrap, antes de qualquer provider começar a emitir logs. Na prática, isso significa que a primeira linha escrita pelo seu bot já sai com o formato de timestamp, os níveis customizados e os transports corretos.
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'],
},
};import { defineConfig } from '@spraxium/core';
import { loggerConfig } from './config/logger.config';
export default defineConfig((_env) => ({
logger: loggerConfig,
}));O array levels registra níveis de log customizados com suas cores no console. Esses níveis passam a ficar disponíveis via Logger.extend e são entendidos pelos dois transports nativos. Já o maskTokens controla o TokenMasker, que vem ligado por padrão. Só faz sentido desligá-lo em sessões locais muito controladas.
| Campo | Tipo | Padrão | Finalidade |
|---|---|---|---|
| levels | CustomLogLevel[] | [] | Nomes de níveis customizados e suas cores no console. |
| maskTokens | boolean | true | Oculta padrões de token do Discord em todas as mensagens de log. |
| discord | DiscordTransportConfig | undefined | Configuração para enviar logs ao Discord. Omitir esse bloco desativa o envio. |
| commandLogging | boolean | false | Quando ativo, registra um listener que loga toda execução de slash command no nível command. Veja Command logging abaixo. |
| timestampFormat | default | iso | time-only | custom formatter | default | Controla como o prefixo de timestamp aparece no console. Veja Formato do timestamp abaixo. |
Não desligue o masking em ambientes compartilhados
Se o log puder parar em output de CI, terminais compartilhados ou canais do Discord, mantenha o mascaramento ativo. É muito mais barato revelar um valor de propósito em um teste local temporário do que corrigir um token de bot que vazou.
Formato do timestamp
A opção timestampFormat controla o prefixo mostrado antes de cada linha de log no console. Você pode escolher um preset pronto, para seguir o padrão do restante do framework, ou fornecer uma função customizada se o bot precisar obedecer a um padrão local já existente.
| Valor | Exemplo de saída | Quando faz mais sentido |
|---|---|---|
| default | 12/04/2026 - 14:32:05 | Boa escolha para a maioria dos bots e para desenvolvimento local. |
| iso | 2026-04-12T14:32:05.000Z | Melhor para envio, parsing e pipelines legíveis por máquina. |
| time-only | 14:32:05 | Útil em sessões curtas de desenvolvimento, quando a data só adiciona ruído. |
| custom formatter | Qualquer string retornada pela função | Use quando precisar seguir um formato próprio ou já adotado pela casa. |
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' }),
};O transport do Discord
O transport do Discord encaminha entradas de log selecionadas como embeds para o Discord. Ele só envia os níveis listados no próprio array levels, então você pode manter logs operacionais de alto volume no terminal e encaminhar apenas alertas, erros ou auditoria de comando para um canal compartilhado.
Existem dois modos de entrega. No modo channel, o transport usa o próprio client do bot para localizar o canal e enviar embeds. No modo webhook, ele faz requisições HTTP para uma URL de webhook, o que é útil quando o destino operacional fica separado do runtime do bot.
| Modo | Melhor quando | Trade-off |
|---|---|---|
| channel | O bot já está no servidor e você quer evitar mais um segredo externo. | A entrega depende do lifecycle do client e das permissões do canal. |
| webhook | Você quer credenciais isoladas de entrega ou precisa logar fora da footprint de guilds do bot. | Você passa a gerenciar uma URL de webhook como segredo extra. |
import type { SpraxiumLoggerConfig } from '@spraxium/logger';
export const loggerConfig: SpraxiumLoggerConfig = {
discord: {
type: 'channel',
channelId: '1234567890123456789',
levels: ['error', 'warn'],
},
};No modo channel, o transport enfileira mensagens que chegam antes de o client ficar pronto. Assim que o client emite ready, a fila é drenada automaticamente. O tamanho máximo é 100 entradas para evitar crescimento de memória durante sequências longas de startup.
O transport também faz deduplicação. Se o mesmo nível, contexto e mensagem forem enviados dentro de um segundo, o segundo envio é suprimido. Isso protege canais operacionais contra rajadas duplicadas causadas por retries ou handlers sobrepostos.
Templates de embed customizados
Por padrão, os embeds do Discord usam o nível como título, a mensagem como descrição, uma cor correspondente e timestamp em formato ISO. Você pode sobrescrever isso com um template customizado em embed quando quiser uma apresentação mais operacional, por exemplo exibindo shard, guild ou campos de metadata em posições previsíveis.
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 },
],
},
},
};Os placeholders disponíveis são resolvidos a partir da entrada de log no momento do envio. A variável shard mostra o ID do shard quando o bot está sharded ou N/A em processo único. Metadata passada por log também vira placeholder, o que torna logs de comando e auditoria especialmente úteis no Discord.
| Placeholder | Origem |
|---|---|
| level | Nome do nível em maiúsculas, como INFO ou ERROR |
| message | Texto da mensagem de log |
| context | Nome do contexto do logger, vazio se não existir |
| shard | ID do shard ou N/A |
| timestamp | Timestamp ISO 8601 |
| date | Formatado como DD/MM/YYYY |
| time | Formatado como HH:MM:SS em 24 horas |
Command logging
O serviço CommandLogger registra automaticamente toda execução de slash command. Ele se conecta ao evento interactionCreate do client e captura nome do comando, tag do usuário, ID do usuário, nome da guild, ID da guild e ID do canal. Cada execução gera uma entrada de log no nível command com metadata estruturada, pronta para reaproveitamento em templates de embed.
A forma padrão de ativar isso é em spraxium.config.ts. Defina commandLogging como true dentro do bloco do logger, e o framework chama CommandLogger.bind automaticamente durante o wiring do client. Esse é o setup mais indicado para a maioria dos bots porque permanece dentro do lifecycle normal do framework.
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}})' },
},
},
};Se você estiver usando o logger fora do framework, ou precisar ligar o command logging manualmente, chame CommandLogger.bind diretamente com o client do Discord depois de ele ficar pronto. A partir daí, metadata de comando, usuário, guild e canal também passa a ficar disponível no template.
import { CommandLogger } from '@spraxium/logger';
// Call after the client is ready
CommandLogger.bind(client);Transportes customizados
Crie um transport customizado quando console e Discord não forem suficientes, por exemplo para escrever em arquivo, integrar com uma stack de observabilidade ou publicar em barramentos internos. Um transport implementa LogTransport, expõe um name e fornece um método log que recebe uma LogEntry.
Falhas do transport são isoladas do restante da aplicação. Se um transport lançar exceção ou rejeitar promessa, o logger engole essa falha para garantir que logging nunca vire ponto único de falha do runtime do bot.
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);
}
}Registre o transport com Logger.addTransport. Se já existir outro com o mesmo nome, ele é substituído. Você pode remover um transport pelo nome com Logger.removeTransport. O ConsoleTransport sempre fica registrado por padrão e usa o nome "console".
import { Logger } from '@spraxium/logger';
import { FileTransport } from './transports/file.transport';
Logger.addTransport(new FileTransport('./logs/bot.log'));Se o seu transport precisar da instância do client do Discord, por exemplo para resolver nome de canal ou dados de guild, implemente a interface ClientAwareTransport no lugar de um transport simples. Ela adiciona o método setClient, chamado automaticamente pelo framework quando o client fica pronto. O DiscordTransport nativo usa essa interface internamente.