feat(logging): внедрено логирование через winston

- Добавлен модуль logger.js с настройками winston для логирования в файлы и консоль
- Заменены все console.log/warn/error в bot.js на вызовы logger.info/warn/error
- Добавлена зависимость winston в package.json
- Логи теперь структурированы, с таймстампами и цветовой подсветкой в консоли
- Улучшена читаемость и удобство отладки приложения
This commit is contained in:
Vufer 2025-06-27 18:58:17 +03:00
parent 1fb3805794
commit 31b7e14f75
3 changed files with 75 additions and 31 deletions

61
bot.js
View File

@ -3,6 +3,7 @@ const fs = require('fs').promises;
const path = require('path');
const { OpenRouterClient } = require('openrouter-kit');
const { getPrompt } = require('./prompts.js');
const logger = require('./logger.js');
require('dotenv').config();
const char = {name:'marina'}
class TelegramHistoryBot {
@ -13,7 +14,7 @@ class TelegramHistoryBot {
// Инициализация OpenRouter с проверкой ключа
if (!process.env.OPENROUTER_API_KEY) {
console.warn('⚠️ OPENROUTER_API_KEY не найден, ИИ-суммаризация будет недоступна');
logger.warn('⚠️ OPENROUTER_API_KEY не найден, ИИ-суммаризация будет недоступна');
this.openRouter = null;
} else {
this.openRouter = new OpenRouterClient({
@ -37,9 +38,9 @@ class TelegramHistoryBot {
try {
await this.loadHistory();
this.setupHandlers();
console.log('✅ Bot запущен и готов к работе');
logger.info('✅ Bot запущен и готов к работе');
} catch (error) {
console.error('❌ Ошибка инициализации бота:', error);
logger.error('❌ Ошибка инициализации бота:', error);
process.exit(1);
}
}
@ -48,9 +49,9 @@ class TelegramHistoryBot {
try {
const data = await fs.readFile(this.historyFile, 'utf8');
this.history = JSON.parse(data);
console.log(`📚 Загружено ${this.history.length} сообщений из истории`);
logger.info(`📚 Загружено ${this.history.length} сообщений из истории`);
} catch (error) {
console.log('📝 История не найдена, создаем новую');
logger.warn('📝 История не найдена, создаем новую');
this.history = [];
await this.saveHistory();
}
@ -60,7 +61,7 @@ class TelegramHistoryBot {
try {
await fs.writeFile(this.historyFile, JSON.stringify(this.history, null, 2));
} catch (error) {
console.error('❌ Ошибка сохранения истории:', error);
logger.error('❌ Ошибка сохранения истории:', error);
}
}
@ -72,7 +73,7 @@ class TelegramHistoryBot {
// await ctx.deleteMessage
// return
// }
console.log('📊 Получена команда summary_day');
logger.info('📊 Получена команда summary_day');
const args = ctx.message.text.split(' ');
if (args.length > 1) {
char.name=args[1]
@ -82,7 +83,7 @@ class TelegramHistoryBot {
});
this.bot.command('summary_hours', async (ctx) => {
console.log('📊 Получена команда summary_hours');
logger.info('📊 Получена команда summary_hours');
const args = ctx.message.text.split(' ');
if (args.length < 2 || isNaN(parseInt(args[1]))) {
await ctx.reply('❗ Укажите количество часов: /summary_hours 6');
@ -94,7 +95,7 @@ class TelegramHistoryBot {
});
this.bot.command('summary_last', async (ctx) => {
console.log('📊 Получена команда summary_last');
logger.info('📊 Получена команда summary_last');
const args = ctx.message.text.split(' ');
if (args.length < 2 || isNaN(parseInt(args[1]))) {
await ctx.reply('❗ Укажите количество сообщений: /summary_last 50');
@ -120,25 +121,25 @@ class TelegramHistoryBot {
// Обработка ошибок
this.bot.catch(async (err, ctx) => {
console.error('❌ Ошибка бота:', err);
logger.error('❌ Ошибка бота:', err);
try {
await ctx.reply('❌ Произошла ошибка при обработке команды');
} catch (replyError) {
console.error('❌ Не удалось отправить сообщение об ошибке:', replyError);
logger.error('❌ Не удалось отправить сообщение об ошибке:', replyError);
}
});
// Запуск бота
this.bot.launch();
console.log('🚀 Бот запущен');
logger.info('🚀 Бот запущен');
// Graceful stop
process.once('SIGINT', () => {
console.log('🛑 Получен сигнал SIGINT, останавливаем бота...');
logger.warn('🛑 Получен сигнал SIGINT, останавливаем бота...');
this.bot.stop('SIGINT');
});
process.once('SIGTERM', () => {
console.log('🛑 Получен сигнал SIGTERM, останавливаем бота...');
logger.warn('🛑 Получен сигнал SIGTERM, останавливаем бота...');
this.bot.stop('SIGTERM');
});
}
@ -169,9 +170,9 @@ class TelegramHistoryBot {
const userName = messageData.first_name || messageData.username || 'Unknown';
const content = messageData.text || `[${messageData.message_type}]`;
console.log(`💾 Сохранено сообщение от ${userName}: ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`);
logger.info(`💾 Сохранено сообщение от ${userName} в ${msg.chat.id} : ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`);
} catch (error) {
console.error('❌ Ошибка при обработке сообщения:', error);
logger.error('❌ Ошибка при обработке сообщения:', error);
}
}
@ -220,7 +221,7 @@ class TelegramHistoryBot {
}
async handleSummaryCommand(ctx, type, value) {
console.log(`📊 Обработка команды суммаризации: ${type}${value ? ` (${value})` : ''}`);
logger.info(`📊 Обработка команды суммаризации: ${type}${value ? ` (${value})` : ''}`);
const chatId = ctx.chat.id;
let messages = [];
@ -250,7 +251,7 @@ class TelegramHistoryBot {
break;
}
console.log(`📈 Найдено ${messages.length} сообщений для суммаризации`);
logger.info(`📈 Найдено ${messages.length} сообщений для суммаризации`);
if (messages.length === 0) {
await ctx.reply('❗ Сообщения для суммаризации не найдены');
@ -267,10 +268,10 @@ class TelegramHistoryBot {
// Отправляем результат
await ctx.reply(summary, { parse_mode: 'HTML' });
console.log('✅ Суммаризация отправлена');
logger.info('✅ Суммаризация отправлена');
} catch (error) {
console.error('❌ Ошибка при создании суммаризации:', error);
logger.error('❌ Ошибка при создании суммаризации:', error);
await ctx.reply('❌ Произошла ошибка при создании суммаризации. Попробуйте позже.');
}
}
@ -388,7 +389,7 @@ class TelegramHistoryBot {
createSummaryPrompt(type, value) {
const timeDescription = this.getTimeDescription(type, value);
console.log('Выбранный персонаж: ', char)
logger.info('Выбранный персонаж: ', char)
return `Ты получишь данные чата Telegram ${timeDescription}. ` +
getPrompt(char.name)
@ -443,13 +444,13 @@ ${conversationFlow}`;
async callAISummarization(prompt, data) {
// Если API ключ недоступен, используем fallback
if (!process.env.OPENROUTER_API_KEY) {
console.log('⚠️ OpenRouter API ключ недоступен, используем базовую суммаризацию');
logger.warn('⚠️ OpenRouter API ключ недоступен, используем базовую суммаризацию');
return this.generateFallbackSummary(data);
}
try {
const formattedMessages = this.formatMessagesForAI(data);
console.log('🤖 Отправляем запрос к OpenRouter...');
logger.info('🤖 Отправляем запрос к OpenRouter...');
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
@ -491,21 +492,21 @@ ${conversationFlow}`;
throw new Error('Пустой ответ от ИИ');
}
console.log('✅ Получен ответ от OpenRouter');
logger.info('✅ Получен ответ от OpenRouter');
return `🎬 <b>История чата</b>\n\n${aiSummary}`;
} catch (error) {
console.error('❌ Ошибка при вызове OpenRouter API:', error);
logger.error('❌ Ошибка при вызове OpenRouter API:', error);
// Детальная обработка различных типов ошибок
if (error.message.includes('401')) {
console.error('❌ Неверный API ключ OpenRouter');
logger.error('❌ Неверный API ключ OpenRouter');
} else if (error.message.includes('429')) {
console.error('❌ Превышен лимит запросов OpenRouter');
logger.error('❌ Превышен лимит запросов OpenRouter');
} else if (error.message.includes('402')) {
console.error('❌ Недостаточно средств на счету OpenRouter');
logger.error('❌ Недостаточно средств на счету OpenRouter');
} else if (error.code === 'ENOTFOUND') {
console.error('❌ Проблемы с сетевым подключением');
logger.error('❌ Проблемы с сетевым подключением');
}
return this.generateFallbackSummary(data);
@ -558,7 +559,7 @@ ${conversationFlow}`;
const BOT_TOKEN = process.env.BOT_TOKEN;
if (!BOT_TOKEN) {
console.error('❌ Укажите токен бота в переменной окружения BOT_TOKEN');
logger.error('❌ Укажите токен бота в переменной окружения BOT_TOKEN');
process.exit(1);
}

42
logger.js Normal file
View File

@ -0,0 +1,42 @@
const winston = require('winston');
const path = require('path');
// __dirname уже доступен в CommonJS, нет нужды использовать fileURLToPath
// Формат для файла (подробный JSON)
const fileFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.json()
);
// Формат для консоли (краткий, с цветами)
const consoleFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({ format: 'HH:mm:ss' }),
winston.format.printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
})
);
const logger = winston.createLogger({
level: 'info',
transports: [
// Логирование ошибок в отдельный файл
new winston.transports.File({
filename: path.join(__dirname, '../logs/error.log'),
level: 'error',
format: fileFormat
}),
// Общий лог файл
new winston.transports.File({
filename: path.join(__dirname, '../logs/combined.log'),
format: fileFormat
}),
// Консольный вывод
new winston.transports.Console({
format: consoleFormat
})
]
});
module.exports = logger;

View File

@ -11,7 +11,8 @@
"dotenv": "^16.5.0",
"openrouter-kit": "^0.1.65",
"process": "^0.11.10",
"telegraf": "^4.12.2"
"telegraf": "^4.12.2",
"winston": "^3.17.0"
},
"devDependencies": {
"nodemon": "^3.0.1"