Spaces:
Sleeping
Sleeping
import fs from 'fs'; | |
import path from 'path'; | |
import { promisify } from 'util'; | |
import { format } from 'date-fns'; | |
import mongoose from 'mongoose'; | |
import ExcelJS from 'exceljs'; | |
import { d as private_env } from './shared-server-49TKSBDM.js'; | |
const LogSchema = new mongoose.Schema({ | |
llmPrompt: { type: String }, | |
llmTemplate: { type: String }, | |
llmResponse: { type: String }, | |
searchResults: { type: [String] }, | |
selectedSearchResults: { type: [String] }, | |
uiSettings: { type: Object }, | |
consultations: { type: [String] }, | |
userRequest: { type: String }, | |
userScore: { type: String }, | |
userComment: { type: String }, | |
dateCreated: { type: Date, default: Date.now }, | |
dateUpdated: { type: Date, default: Date.now } | |
}); | |
LogSchema.index({ dateCreated: 1 }, { unique: false }); | |
const LogModel = mongoose.models.Log || mongoose.model("Log", LogSchema); | |
class MongooseService { | |
uri; | |
options; | |
connection; | |
constructor(uri, options = {}) { | |
try { | |
this.uri = uri || process.env.MONGODB_URI || ""; | |
this.options = options; | |
this.connection = null; | |
if (!this.uri) { | |
console.warn("MongoDB URI is not defined"); | |
} | |
} catch { | |
console.warn("MongoDB URI is not defined. Logs wont be saved."); | |
} | |
} | |
/** | |
* Connect to the MongoDB database | |
* @returns {Promise<Connection>} | |
*/ | |
async connect() { | |
if (this.connection) { | |
return this.connection; | |
} | |
try { | |
await mongoose.connect(this.uri, this.options); | |
this.connection = mongoose.connection; | |
console.log("Connected to MongoDB"); | |
return this.connection; | |
} catch (error) { | |
console.warn("MongoDB URI is not defined. Logs wont be saved."); | |
} | |
} | |
/** | |
* Disconnect from the MongoDB database | |
* @returns {Promise<void>} | |
*/ | |
async disconnect() { | |
if (!this.connection) { | |
console.log("No active MongoDB connection to disconnect"); | |
return; | |
} | |
try { | |
await mongoose.disconnect(); | |
this.connection = null; | |
console.log("Disconnected from MongoDB"); | |
} catch (error) { | |
console.error("Error disconnecting from MongoDB:", error); | |
throw new Error("Could not disconnect from MongoDB"); | |
} | |
} | |
/** | |
* Get the current MongoDB connection | |
* @returns {Connection | null} | |
*/ | |
async getConnection() { | |
if (!this.isConnected()) { | |
await this.connect(); | |
} | |
return this.connection; | |
} | |
isConnected() { | |
return this.connection !== null && mongoose.connection.readyState === 1; | |
} | |
} | |
const ExcelColumnsName = { | |
llmPrompt: "Промпт", | |
llmTemplate: "Шаблон", | |
llmResponse: "Ответ LLM", | |
searchResults: "Результаты поиска", | |
selectedSearchResults: "Выбранные результаты поиска", | |
uiSettings: "Параметры", | |
consultations: "Релевантные консультации", | |
userRequest: "Запрос", | |
userScore: "Оценка пользователя", | |
userComment: "Комментарий пользователя", | |
dateCreated: "Дата создания", | |
dateUpdated: "Дата изменения" | |
}; | |
const writeFile = promisify(fs.writeFile); | |
const readDir = promisify(fs.readdir); | |
const readFile = promisify(fs.readFile); | |
class LogService { | |
mongooseService; | |
logModel; | |
constructor() { | |
if (private_env.ENABLE_DB_SUPPORT) { | |
this.mongooseService = new MongooseService(private_env.MONGODB_URI || ""); | |
this.logModel = LogModel; | |
} | |
} | |
/** | |
* Сохраняет лог в базу данных MongoDB, если она доступна. | |
* @param document - Лог-документ, который нужно сохранить. | |
* @returns {Promise<string>} ID созданной записи. | |
*/ | |
async log(document) { | |
if (!private_env.ENABLE_DB_SUPPORT) { | |
return ""; | |
} | |
try { | |
await this.mongooseService.getConnection(); | |
const logEntry = new this.logModel(document); | |
await logEntry.save(); | |
console.log("Log entry saved to MongoDB"); | |
return logEntry._id; | |
} catch (err) { | |
console.error("MongoDB connection failed", err); | |
} | |
return ""; | |
} | |
/** | |
* Обновляет поле userScore в записи с указанным ID. | |
* @param id - ID записи, которую нужно обновить. | |
* @param score - Новое значение для userScore. | |
* @param comment - Новое значение для userComment. | |
* @returns Количество обновленных записей. | |
*/ | |
async logUserScore(id, score, comment) { | |
if (!private_env.ENABLE_DB_SUPPORT) { | |
return ""; | |
} | |
try { | |
await this.mongooseService.getConnection(); | |
const LogModel2 = mongoose.model("Log"); | |
const result = await LogModel2.updateOne( | |
{ _id: id }, | |
{ $set: { userScore: score, userComment: comment } } | |
); | |
return result.modifiedCount; | |
} catch (error) { | |
console.error("Error while updating userScore:", error); | |
throw error; | |
} | |
} | |
/** | |
* Сохраняет лог-документ в файл JSON. | |
* Имя файла основано на текущем времени. | |
* @param document - Лог-документ, который нужно сохранить. | |
* @returns {Promise<void>} | |
*/ | |
async saveLogToJsonFile(document) { | |
const timestamp = format(/* @__PURE__ */ new Date(), "dd-MM-yyyy:HH:mm:ss:SSS"); | |
const logDir = path.join(private_env.LOGS_ROOT_FOLDER, "log"); | |
const logFile = path.join(logDir, `${timestamp}.json`); | |
if (!fs.existsSync(logDir)) { | |
fs.mkdirSync(logDir, { recursive: true }); | |
} | |
const data = JSON.stringify(document, null, 2); | |
await writeFile(logFile, data); | |
console.log(`Log entry saved to file: ${logFile}`); | |
} | |
/** | |
* Экспортирует логи из базы данных и/или файлов в формате JSON или Excel. | |
* Записи сортируются по дате создания. Если указаны dateFrom и dateTo, применяется фильтрация по дате. | |
* @param type - Тип экспорта: 'json' или 'excel'. | |
* @param dateFrom - Дата начала фильтрации (необязательно). | |
* @param dateTo - Дата окончания фильтрации (необязательно). | |
* @param fields - Поля для экспорта | |
* @returns {Promise<any>} | |
*/ | |
async export(type, dateFrom, dateTo, fields) { | |
if (!private_env.ENABLE_DB_SUPPORT) { | |
return ""; | |
} | |
const mongoLogs = await this.fetchLogsFromMongo(dateFrom, dateTo, fields); | |
const allLogs = [...mongoLogs]; | |
if (type === "json") { | |
return await this.exportToJson(allLogs); | |
} else if (type === "excel") { | |
return await this.exportToExcel(allLogs); | |
} | |
} | |
/** | |
* Извлекает логи из базы данных MongoDB с учетом фильтрации по дате. | |
* @param dateFrom - Дата начала фильтрации (необязательно). | |
* @param dateTo - Дата окончания фильтрации (необязательно). | |
* @returns {Promise<any[]>} - Возвращает массив логов. | |
*/ | |
async fetchLogsFromMongo(dateFrom, dateTo, fields) { | |
if (!private_env.ENABLE_DB_SUPPORT) { | |
return []; | |
} | |
const query = {}; | |
if (dateFrom || dateTo) { | |
query.dateCreated = {}; | |
if (dateFrom) | |
query.dateCreated.$gte = dateFrom; | |
if (dateTo) | |
query.dateCreated.$lte = dateTo; | |
} | |
await this.mongooseService.getConnection(); | |
return await this.logModel.find(query).select(fields ? fields.join(" ") : "").sort({ dateCreated: 1 }).lean().exec(); | |
} | |
/** | |
* Извлекает логи из файлов JSON в папке /data/log с учетом фильтрации по дате. | |
* @param dateFrom - Дата начала фильтрации (необязательно). | |
* @param dateTo - Дата окончания фильтрации (необязательно). | |
* @returns {Promise<any[]>} - Возвращает массив логов. | |
*/ | |
async fetchLogsFromFiles(dateFrom, dateTo) { | |
const logDir = path.join(__dirname, "../data/log"); | |
const files = await readDir(logDir); | |
const logs = []; | |
for (const file of files) { | |
const filePath = path.join(logDir, file); | |
const content = await readFile(filePath, "utf-8"); | |
const logEntry = JSON.parse(content); | |
const fileDate = format( | |
new Date(file.split(".")[0].replace(/-/g, ":")), | |
"yyyy-MM-dd HH:mm:ss:SSS" | |
); | |
if ((!dateFrom || new Date(fileDate) >= dateFrom) && (!dateTo || new Date(fileDate) <= dateTo)) { | |
logs.push(logEntry); | |
} | |
} | |
logs.sort((a, b) => new Date(a.dateCreated).getTime() - new Date(b.dateCreated).getTime()); | |
return logs; | |
} | |
// /** | |
// * Экспортирует массив логов в файл JSON. | |
// * @param logs - Массив логов, который нужно экспортировать. | |
// * @returns {Promise<void>} | |
// */ | |
// private async exportToJson(logs: any[]): Promise<void> { | |
// const exportFile = path.join(__dirname, '../data/export/logs.json'); | |
// await writeFile(exportFile, JSON.stringify(logs, null, 2)); | |
// console.log(`Logs exported to JSON file: ${exportFile}`); | |
// } | |
/** | |
* Экспортирует массив логов в файл Excel. | |
* @param logs - Массив логов, который нужно экспортировать. | |
* @returns {Promise<void>} | |
*/ | |
async exportToExcel(logs) { | |
const workbook = new ExcelJS.Workbook(); | |
const worksheet = workbook.addWorksheet("Logs"); | |
if (private_env.ENABLE_DB_SUPPORT) { | |
worksheet.columns = Object.keys(logs[0] ?? {}).map((key) => { | |
return { | |
header: ExcelColumnsName[key] ?? key, | |
key, | |
width: 30 | |
}; | |
}); | |
logs.forEach((log) => { | |
const consultations = (log.consultations ?? []).map((v) => JSON.stringify(v)); | |
const searchResults = (log.searchResults ?? []).map((v) => JSON.stringify(v)); | |
const selectedSearchResults = (log.selectedSearchResults ?? []).map( | |
(v) => JSON.stringify(v) | |
); | |
const serializedLog = Object.fromEntries( | |
Object.entries(log).map(([key, value]) => [ | |
key, | |
typeof value === "object" && value !== null ? JSON.stringify(value) : value | |
]) | |
); | |
worksheet.addRow({ | |
...serializedLog, | |
consultations: consultations[0] ?? "", | |
searchResults: searchResults[0] ?? "", | |
selectedSearchResults: selectedSearchResults[0] ?? "" | |
}); | |
const maxLength = Math.max( | |
consultations.length, | |
searchResults.length, | |
selectedSearchResults.length | |
); | |
for (let index = 1; index < maxLength; index++) { | |
worksheet.addRow({ | |
consultations: consultations[index] ?? "", | |
searchResults: searchResults[index] ?? "", | |
selectedSearchResults: selectedSearchResults[index] ?? "" | |
}); | |
} | |
}); | |
} | |
const buffer = await workbook.xlsx.writeBuffer(); | |
return buffer; | |
} | |
} | |
export { LogService as L }; | |
//# sourceMappingURL=LogService-6IleCGNg.js.map | |