feat: integrate pino logger for improved error handling and logging consistency
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
import { REST } from "discord.js";
|
||||
// @ts-ignore
|
||||
import { Routes } from "discord-api-types/v10";
|
||||
@@ -19,12 +20,12 @@ export async function registeringCommands(): Promise<void> {
|
||||
options: cmd.options ?? []
|
||||
});
|
||||
|
||||
console.log(`✅ Preparado para registrar (guild): ${cmd.name}`);
|
||||
logger.info(`✅ Preparado para registrar (guild): ${cmd.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`🧹 Limpiando comandos antiguos/residuales (guild)...`);
|
||||
logger.info(`🧹 Limpiando comandos antiguos/residuales (guild)...`);
|
||||
|
||||
// Primero eliminamos TODOS los comandos existentes
|
||||
await rest.put(
|
||||
@@ -35,11 +36,11 @@ export async function registeringCommands(): Promise<void> {
|
||||
{ body: [] } // Array vacío elimina todos los comandos
|
||||
);
|
||||
|
||||
console.log(`✅ Comandos antiguos de guild eliminados.`);
|
||||
logger.info(`✅ Comandos antiguos de guild eliminados.`);
|
||||
// Pequeña pausa para asegurar que Discord procese la eliminación
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
console.log(`🚀 Registrando ${commandsToRegister.length} comandos slash nuevos (guild)...`);
|
||||
logger.info(`🚀 Registrando ${commandsToRegister.length} comandos slash nuevos (guild)...`);
|
||||
|
||||
// Ahora registramos los comandos actuales
|
||||
const data: any = await rest.put(
|
||||
@@ -50,9 +51,9 @@ export async function registeringCommands(): Promise<void> {
|
||||
{ body: commandsToRegister }
|
||||
);
|
||||
|
||||
console.log(`✅ ${data.length} comandos de guild registrados.`);
|
||||
logger.info(`✅ ${data.length} comandos de guild registrados.`);
|
||||
} catch (error) {
|
||||
console.error("❌ Error en el proceso de comandos de guild:", error);
|
||||
logger.error("❌ Error en el proceso de comandos de guild:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,31 +67,31 @@ export async function registeringGlobalCommands(): Promise<void> {
|
||||
type: 1,
|
||||
options: cmd.options ?? []
|
||||
});
|
||||
console.log(`🌍 Preparado para registrar global: ${cmd.name}`);
|
||||
logger.info(`🌍 Preparado para registrar global: ${cmd.name}`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
console.log(`🧹 Limpiando comandos globales existentes...`);
|
||||
logger.info(`🧹 Limpiando comandos globales existentes...`);
|
||||
await rest.put(
|
||||
Routes.applicationCommands(process.env.CLIENT!),
|
||||
{ body: [] }
|
||||
);
|
||||
console.log(`✅ Comandos globales previos eliminados.`);
|
||||
logger.info(`✅ Comandos globales previos eliminados.`);
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
console.log(`🚀 Registrando ${commandsToRegister.length} comandos globales... (propagación puede tardar hasta 1h)`);
|
||||
logger.info(`🚀 Registrando ${commandsToRegister.length} comandos globales... (propagación puede tardar hasta 1h)`);
|
||||
const data: any = await rest.put(
|
||||
Routes.applicationCommands(process.env.CLIENT!),
|
||||
{ body: commandsToRegister }
|
||||
);
|
||||
console.log(`✅ ${data.length} comandos globales enviados a la API.`);
|
||||
logger.info(`✅ ${data.length} comandos globales enviados a la API.`);
|
||||
} catch (error) {
|
||||
console.error("❌ Error registrando comandos globales:", error);
|
||||
logger.error("❌ Error registrando comandos globales:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearAllCommands(): Promise<void> {
|
||||
try {
|
||||
console.log(`🧹 Eliminando TODOS los comandos slash (guild)...`);
|
||||
logger.info(`🧹 Eliminando TODOS los comandos slash (guild)...`);
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(
|
||||
process.env.CLIENT!,
|
||||
@@ -98,21 +99,21 @@ export async function clearAllCommands(): Promise<void> {
|
||||
),
|
||||
{ body: [] }
|
||||
);
|
||||
console.log(`✅ Todos los comandos de guild eliminados.`);
|
||||
logger.info(`✅ Todos los comandos de guild eliminados.`);
|
||||
} catch (error) {
|
||||
console.error("❌ Error eliminando comandos de guild:", error);
|
||||
logger.error("❌ Error eliminando comandos de guild:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearGlobalCommands(): Promise<void> {
|
||||
try {
|
||||
console.log(`🌍 Eliminando comandos globales...`);
|
||||
logger.info(`🌍 Eliminando comandos globales...`);
|
||||
await rest.put(
|
||||
Routes.applicationCommands(process.env.CLIENT!),
|
||||
{ body: [] }
|
||||
);
|
||||
console.log(`✅ Comandos globales eliminados.`);
|
||||
logger.info(`✅ Comandos globales eliminados.`);
|
||||
} catch (error) {
|
||||
console.error("❌ Error eliminando comandos globales:", error);
|
||||
logger.error("❌ Error eliminando comandos globales:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sdk: any = require('node-appwrite');
|
||||
import type Amayo from '../client';
|
||||
@@ -62,7 +63,7 @@ async function fetchDueReminders(limit = 25): Promise<ReminderRow[]> {
|
||||
]) as unknown as { documents?: ReminderRow[] };
|
||||
return (list.documents || []) as ReminderRow[];
|
||||
} catch (e) {
|
||||
console.error('Error listando recordatorios vencidos:', e);
|
||||
logger.error('Error listando recordatorios vencidos:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ async function deliverReminder(bot: Amayo, doc: ReminderRow) {
|
||||
delivered = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('No se pudo enviar al canal original:', e);
|
||||
logger.warn('No se pudo enviar al canal original:', e);
|
||||
}
|
||||
}
|
||||
// 2) Fallback: DM al usuario
|
||||
@@ -92,7 +93,7 @@ async function deliverReminder(bot: Amayo, doc: ReminderRow) {
|
||||
await user.send({ content: `⏰ Recordatorio: ${message}` });
|
||||
delivered = true;
|
||||
} catch (e) {
|
||||
console.warn('No se pudo enviar DM al usuario:', e);
|
||||
logger.warn('No se pudo enviar DM al usuario:', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,12 +102,12 @@ async function deliverReminder(bot: Amayo, doc: ReminderRow) {
|
||||
|
||||
export function startReminderPoller(bot: Amayo) {
|
||||
if (!isAppwriteConfigured()) {
|
||||
console.warn('Appwrite no configurado: el poller de recordatorios no se iniciará.');
|
||||
logger.warn('Appwrite no configurado: el poller de recordatorios no se iniciará.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const intervalSec = parseInt(process.env.REMINDERS_POLL_INTERVAL_SECONDS || '30', 10);
|
||||
console.log(`⏱️ Iniciando poller de recordatorios cada ${intervalSec}s`);
|
||||
logger.info(`⏱️ Iniciando poller de recordatorios cada ${intervalSec}s`);
|
||||
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
@@ -119,11 +120,11 @@ export function startReminderPoller(bot: Amayo) {
|
||||
const db = getDatabases();
|
||||
if (db) await db.deleteDocument(APPWRITE_DATABASE_ID, APPWRITE_COLLECTION_REMINDERS_ID, d.$id);
|
||||
} catch (e) {
|
||||
console.warn('No se pudo eliminar recordatorio entregado:', e);
|
||||
logger.warn('No se pudo eliminar recordatorio entregado:', e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error en ciclo de recordatorios:', e);
|
||||
logger.error('Error en ciclo de recordatorios:', e);
|
||||
}
|
||||
}, Math.max(10, intervalSec) * 1000);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const sdk: any = require('node-appwrite');
|
||||
import { getDatabases, isAppwriteConfigured, APPWRITE_COLLECTION_REMINDERS_ID, APPWRITE_DATABASE_ID } from './appwrite';
|
||||
@@ -20,7 +21,7 @@ export async function ensureRemindersSchema() {
|
||||
]);
|
||||
// Nota: No añadimos permisos de lectura pública para evitar fuga de datos
|
||||
} catch (e) {
|
||||
console.warn('No se pudo crear la colección de recordatorios (puede existir ya):', e);
|
||||
logger.warn('No se pudo crear la colección de recordatorios (puede existir ya):', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +31,7 @@ export async function ensureRemindersSchema() {
|
||||
const msg = String(e?.message || e);
|
||||
if (!/already exists|attribute_already_exists/i.test(msg)) {
|
||||
// Otros errores se muestran
|
||||
console.warn('No se pudo crear atributo:', msg);
|
||||
logger.warn('No se pudo crear atributo:', msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -49,7 +50,7 @@ export async function ensureRemindersSchema() {
|
||||
} catch (e: any) {
|
||||
const msg = String(e?.message || e);
|
||||
if (!/already exists|index_already_exists/i.test(msg)) {
|
||||
console.warn('No se pudo crear índice executeAt:', msg);
|
||||
logger.warn('No se pudo crear índice executeAt:', msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Client, GatewayIntentBits, Options, Partials } from 'discord.js';
|
||||
import { prisma, ensurePrismaConnection } from './database/prisma';
|
||||
import logger from './lib/logger';
|
||||
|
||||
// Verificar si process.loadEnvFile existe (Node.js 20.6+)
|
||||
if (typeof process.loadEnvFile === 'function') {
|
||||
@@ -9,6 +10,7 @@ if (typeof process.loadEnvFile === 'function') {
|
||||
class Amayo extends Client {
|
||||
public key: string;
|
||||
public prisma = prisma;
|
||||
public mode: string;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
@@ -44,19 +46,20 @@ class Amayo extends Client {
|
||||
});
|
||||
|
||||
this.key = process.env.TOKEN ?? '';
|
||||
this.mode = process.env.MODE ?? 'Normal';
|
||||
}
|
||||
|
||||
async play() {
|
||||
if (!this.key) {
|
||||
console.error('No key provided');
|
||||
logger.error('No key provided');
|
||||
throw new Error('Missing DISCORD TOKEN');
|
||||
} else {
|
||||
try {
|
||||
await ensurePrismaConnection();
|
||||
console.log('Successfully connected to the database (singleton).');
|
||||
logger.info('Successfully connected to the database (singleton).');
|
||||
await this.login(this.key);
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to DB or login to Discord:', error);
|
||||
logger.error({ err: error }, 'Failed to connect to DB or login to Discord');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createClient } from "redis";
|
||||
import logger from "../lib/logger";
|
||||
|
||||
export const redis = createClient({
|
||||
username: 'default',
|
||||
@@ -9,9 +10,9 @@ export const redis = createClient({
|
||||
}
|
||||
});
|
||||
|
||||
redis.on("error", (err: any) => console.error("Redis error:", err));
|
||||
redis.on("connect", () => console.log("✅ Conectado a Redis"));
|
||||
redis.on("reconnecting", () => console.warn("♻️ Reintentando conexión Redis"));
|
||||
redis.on("error", (err: any) => logger.error({ err }, "Redis error"));
|
||||
redis.on("connect", () => logger.info("✅ Conectado a Redis"));
|
||||
redis.on("reconnecting", () => logger.warn("♻️ Reintentando conexión Redis"));
|
||||
|
||||
export async function redisConnect () {
|
||||
if (!redis.isOpen) await redis.connect();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "./logger";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { Collection } from "discord.js";
|
||||
@@ -11,7 +12,7 @@ export const contextmenus: Collection<string, ContextMenu> = new Collection<stri
|
||||
export function loadComponents(dir: string = path.resolve(__dirname, "../../components")) {
|
||||
// Evitar fallo si el directorio no existe en el entorno
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.warn(`⚠️ Directorio de componentes no encontrado: ${dir}`);
|
||||
logger.warn(`⚠️ Directorio de componentes no encontrado: ${dir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,28 +35,28 @@ export function loadComponents(dir: string = path.resolve(__dirname, "../../comp
|
||||
const component = imported.default ?? imported;
|
||||
|
||||
if (!component?.customId) {
|
||||
console.warn(`⚠️ Archivo ignorado: ${file} (no tiene "customId")`);
|
||||
logger.warn(`⚠️ Archivo ignorado: ${file} (no tiene "customId")`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detectamos el tipo según la carpeta en la que está
|
||||
if (fullPath.includes("buttons")) {
|
||||
buttons.set(component.customId, component as Button);
|
||||
console.log(`🔘 Botón cargado: ${component.customId}`);
|
||||
logger.info(`🔘 Botón cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("modals")) {
|
||||
modals.set(component.customId, component as Modal);
|
||||
console.log(`📄 Modal cargado: ${component.customId}`);
|
||||
logger.info(`📄 Modal cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("selectmenus")) {
|
||||
selectmenus.set(component.customId, component as SelectMenu);
|
||||
console.log(`📜 SelectMenu cargado: ${component.customId}`);
|
||||
logger.info(`📜 SelectMenu cargado: ${component.customId}`);
|
||||
} else if (fullPath.includes("contextmenu")) {
|
||||
contextmenus.set(component.customId, component as ContextMenu);
|
||||
console.log(`📑 ContextMenu cargado: ${component.customId}`);
|
||||
logger.info(`📑 ContextMenu cargado: ${component.customId}`);
|
||||
} else {
|
||||
console.log(`⚠️ Componente desconocido: ${component.customId}`);
|
||||
logger.info(`⚠️ Componente desconocido: ${component.customId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error cargando componente ${file}:`, error);
|
||||
logger.error(`❌ Error cargando componente ${file}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
src/core/lib/logger.ts
Normal file
22
src/core/lib/logger.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import pino from 'pino';
|
||||
|
||||
// Configurar pino con opciones optimizadas para producción
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
transport: process.env.NODE_ENV !== 'production' ? {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'HH:MM:ss Z',
|
||||
ignore: 'pid,hostname',
|
||||
}
|
||||
} : undefined,
|
||||
formatters: {
|
||||
level: (label) => {
|
||||
return { level: label };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default logger;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
import * as fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { Collection } from "discord.js";
|
||||
@@ -7,7 +8,7 @@ export const commands = new Collection<string, any>();
|
||||
export function loadCommands(dir: string = path.resolve(__dirname, "../../commands")) {
|
||||
// Evitar fallo si el directorio no existe en el entorno
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.warn(`⚠️ Directorio de comandos no encontrado: ${dir}`);
|
||||
logger.warn(`⚠️ Directorio de comandos no encontrado: ${dir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,12 +30,12 @@ export function loadCommands(dir: string = path.resolve(__dirname, "../../comman
|
||||
const command = imported.command ?? imported.default ?? imported;
|
||||
|
||||
if (!command?.data?.name && !command?.name) {
|
||||
console.warn(`⚠️ Archivo ignorado: ${file} (no es un comando válido)`);
|
||||
logger.warn(`⚠️ Archivo ignorado: ${file} (no es un comando válido)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = command.data?.name ?? command.name;
|
||||
console.log(`📦 Loading command: ${name}`);
|
||||
logger.info(`📦 Loading command: ${name}`);
|
||||
|
||||
// @ts-ignore
|
||||
commands.set(name, command);
|
||||
@@ -45,6 +46,6 @@ export function loadCommands(dir: string = path.resolve(__dirname, "../../comman
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Cargado comando: ${name}`);
|
||||
logger.info(`✅ Cargado comando: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { bot } from "../../main";
|
||||
import path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
import logger from "../lib/logger";
|
||||
|
||||
export function loadEvents(dir: string = path.resolve(__dirname, "../../events")) {
|
||||
// Evitar fallo si el directorio no existe
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.warn(`⚠️ Directorio de eventos no encontrado: ${dir}`);
|
||||
logger.warn(`⚠️ Directorio de eventos no encontrado: ${dir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,6 +34,6 @@ export function loadEvents(dir: string = path.resolve(__dirname, "../../events")
|
||||
bot.on(event.name, (...args: any[]) => event.execute(...args));
|
||||
}
|
||||
|
||||
console.log(`Evento cargado: ${event.name}`);
|
||||
logger.info(`Evento cargado: ${event.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
// Monitor ligero de memoria y event loop.
|
||||
// Se activa si defines MEMORY_LOG_INTERVAL_SECONDS.
|
||||
import { monitorEventLoopDelay } from 'node:perf_hooks';
|
||||
@@ -41,7 +42,7 @@ export function startMemoryMonitor(opts: MemoryMonitorOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[MEM] rss=${rss} heapUsed=${heapUsed} heapTotal=${heapTotal} ext=${external} evLoopDelay=${elDelay.toFixed(2)}ms${warn}`);
|
||||
logger.info(`[MEM] rss=${rss} heapUsed=${heapUsed} heapTotal=${heapTotal} ext=${external} evLoopDelay=${elDelay.toFixed(2)}ms${warn}`);
|
||||
|
||||
// Resetear métricas de event loop delay para la siguiente ventana
|
||||
h.reset();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from "../lib/logger";
|
||||
// Sistema adicional de optimización de memoria para complementar el monitor existente
|
||||
|
||||
export interface MemoryOptimizerOptions {
|
||||
@@ -21,7 +22,7 @@ export class MemoryOptimizer {
|
||||
start() {
|
||||
// Solo habilitar si está disponible el GC manual
|
||||
if (typeof global.gc !== 'function') {
|
||||
console.warn('⚠️ Manual GC no disponible. Inicia con --expose-gc para habilitar optimizaciones adicionales.');
|
||||
logger.warn('⚠️ Manual GC no disponible. Inicia con --expose-gc para habilitar optimizaciones adicionales.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ export class MemoryOptimizer {
|
||||
this.gcTimer.unref(); // No bloquear el cierre del proceso
|
||||
}
|
||||
|
||||
console.log(`✅ Memory Optimizer iniciado - GC cada ${this.options.forceGCInterval}min, umbral: ${this.options.maxHeapUsageBeforeGC}MB`);
|
||||
logger.info(`✅ Memory Optimizer iniciado - GC cada ${this.options.forceGCInterval}min, umbral: ${this.options.maxHeapUsageBeforeGC}MB`);
|
||||
}
|
||||
|
||||
stop() {
|
||||
@@ -69,7 +70,7 @@ export class MemoryOptimizer {
|
||||
const duration = Date.now() - startTime;
|
||||
const heapFreed = (before.heapUsed - after.heapUsed) / 1024 / 1024;
|
||||
|
||||
console.log(`🗑️ GC ${reason}: liberó ${heapFreed.toFixed(1)}MB en ${duration}ms`);
|
||||
logger.info(`🗑️ GC ${reason}: liberó ${heapFreed.toFixed(1)}MB en ${duration}ms`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user