LogAnalyze/server_old.js

336 lines
9.6 KiB
JavaScript

// === app.js ===
const express = require("express");
const mysql = require("mysql2/promise");
const bodyParser = require("body-parser");
const axios = require("axios");
const cheerio = require("cheerio");
const dotenv = require("dotenv");
dotenv.config();
const app = express();
const PORT = 4000;
app.use(bodyParser.json());
app.use(express.static("public"));
// === MySQL Connection ===
const db = mysql.createPool({
host: "localhost",
user: "root",
password: "",
database: "log_analysis",
});
// === Create Table If Not Exists ===
(async () => {
await db.query(`
CREATE TABLE IF NOT EXISTS pid_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
pid VARCHAR(100),
version VARCHAR(100),
log TEXT,
commands JSON,
errors LONGTEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
})();
// === Regex-based command detection ===
function extractCommands(log) {
return {
inventory: /sh(ow)?\s+inv(entory)?/i.test(log),
version: /sh(ow)?\s+ver(sion)?/i.test(log),
license: /sh(ow)?\s+lic(ense)?/i.test(log),
logging: /sh(ow)?\s+log(ging)?/i.test(log),
};
}
function extractPIDVersion(log) {
const pidMatch = log.match(/PID:\s*([A-Z0-9\-]+)/);
const versionMatch = log.match(/Version\s+([\d\.A-Za-z\-]+)/);
return {
pid: pidMatch ? pidMatch[1] : "UNKNOWN",
version: versionMatch ? versionMatch[1] : "UNKNOWN",
};
}
function extractLogging(log) {
return log
.split("\n")
.filter(
(line) =>
line.startsWith("$ *") ||
line.startsWith("$ %") ||
line.startsWith("%") ||
line.includes("ALARM")
);
}
function extractRelevantLog(log, commands) {
const lines = log.split('\n');
const commandPatterns = [
{ key: 'inventory', regex: /sh(ow)?\s+inv(entory)?/i },
{ key: 'version', regex: /sh(ow)?\s+ver(sion)?/i },
{ key: 'license', regex: /sh(ow)?\s+lic(ense)?/i },
{ key: 'logging', regex: /sh(ow)?\s+log(ging)?/i },
];
let extracted = '';
const included = new Set();
for (let i = 0; i < lines.length; i++) {
for (const cmd of commandPatterns) {
const { key, regex } = cmd;
if (commands[key] && !included.has(key) && regex.test(lines[i])) {
included.add(key);
// console.log(log);
// Tách prompt và command
const match = lines[i].match(/^(.+?[#>])\s*(sh(ow)?\s+\w+)/i);
let prompt = '', command = '';
if (match) {
prompt = match[1]; // phần như "Switch#", "Router>", ...
command = match[2]; // phần như "show inventory", ...
}
console.log({prompt, command, lines: [lines[i], lines[i+1]]});
let block = [lines[i]];
for (let j = i + 1; j < lines.length; j++) {
// Nếu phát hiện dòng chứa command khác chưa được xử lý, thì dừng lại
// const isNextCommand = commandPatterns.some(c =>
// commands[c.key] && !included.has(c.key) && c.regex.test(lines[j])
// );
if (lines[j].includes(prompt)) break;
// if (isNextCommand) break;
block.push(lines[j]);
}
extracted += block.join('\n') + '\n';
}
}
}
return extracted.trim();
}
// === Search PID ===
app.get("/api/search", async (req, res) => {
const { pid } = req.query;
const [rows] = await db.query("SELECT * FROM pid_logs WHERE pid LIKE ?", [
`%${pid}%`,
]);
res.json(rows);
});
// === Add New Block ===
app.post("/api/add", async (req, res) => {
const { pid, version, log } = req.body;
const commands = extractCommands(log);
const errors = extractLogging(log);
const relevantLog = extractRelevantLog(log, commands);
await db.query(
`INSERT INTO pid_logs (pid, version, log, commands, errors) VALUES (?, ?, ?, ?, ?)`,
[
pid,
version,
`[${filename}]\n${relevantLog}`,
JSON.stringify(commands),
errors.join('\n'),
]
);
res.json({ success: true });
});
// === Ask AI to extract errors ===
app.post("/api/extract-errors", async (req, res) => {
const { log } = req.body;
const response = await axios.post(
"https://api.openai.com/v1/chat/completions",
{
model: "gpt-4o-mini",
messages: [
{
role: "system",
content:
"Extract and summarize hardware or configuration errors from this Cisco log block.",
},
{ role: "user", content: log },
],
},
{
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
}
);
res.json({ errors: response.data.choices[0].message.content });
});
// Get versions for a specific PID
app.get("/api/pid/:pid/versions", async (req, res) => {
const { pid } = req.params;
const [rows] = await db.query(
"SELECT version, commands FROM pid_logs WHERE pid = ?",
[pid]
);
// Nhóm version nếu bị trùng, lấy unique
const seen = new Set();
const result = [];
for (const row of rows) {
if (seen.has(row.version)) continue;
seen.add(row.version);
result.push({
version: row.version,
commands: row.commands,
});
}
res.json(result);
});
// Get full log + errors by pid and version
app.get("/api/pid/:pid/version/:version", async (req, res) => {
const { pid, version } = req.params;
const [rows] = await db.query(
"SELECT log, errors FROM pid_logs WHERE pid = ? AND version = ?",
[pid, version]
);
if (rows.length === 0) return res.status(404).json({ message: "Not found" });
res.json(rows[0]);
});
app.get("/api/pids", async (req, res) => {
const [rows] = await db.query("SELECT DISTINCT pid FROM pid_logs ORDER BY pid");
res.json(rows.map((row) => row.pid));
});
// === Auto crawl logs from internal directory ===
async function fetchLogFile(url) {
const res = await axios.get(url);
return res.data;
}
async function crawlLogs() {
const BASE_URL = "http://172.16.5.7:8080";
console.log(`[${new Date().toISOString()}] Start crawl ${BASE_URL}`);
const res = await axios.get(BASE_URL);
const $ = cheerio.load(res.data);
const links = $("a")
.map((_, el) => $(el).attr("href"))
.get()
.filter((href) => href.endsWith(".log"));
const splitBlocks = (logContent) =>
logContent
.split(/(?=^.*?[#>]\s*sh(ow)?\s+inv(entory)?)/gim)
.map((b) => b?.trim())
.filter(Boolean);
for (const link of links) {
const fileUrl = `${BASE_URL}/${link}`;
const filename = link.replace(/\//g, "_");
// const [exists] = await db.query(
// "SELECT COUNT(*) as count FROM pid_logs WHERE log LIKE ?",
// [`%${filename}%`]
// );
// if (exists[0].count > 0) {
// console.log(`🟡 Skipped: ${filename}`);
// continue;
// }
try {
const log = await fetchLogFile(fileUrl);
const blocks = splitBlocks(log);
for (const block of blocks) {
const { pid, version } = extractPIDVersion(block);
const commands = extractCommands(block);
const errors = extractLogging(block);
if (pid === "UNKNOWN" || version === "UNKNOWN") continue;
const [existing] = await db.query(
"SELECT * FROM pid_logs WHERE pid = ? AND version = ?",
[pid, version]
);
// Nếu CHƯA CÓ -> INSERT mới toàn bộ
if (!existing[0]) {
const relevantLog = extractRelevantLog(block, commands);
await db.query(
`INSERT INTO pid_logs (pid, version, log, commands, errors) VALUES (?, ?, ?, ?, ?)`,
[
pid,
version,
`[${filename}]\n${relevantLog}`,
JSON.stringify(commands),
errors.join('\n'),
]
);
console.log(`✅ Added ${pid} ${version} (${filename})`);
continue;
}
// Nếu ĐÃ CÓ -> Kiểm tra và UPDATE nếu cần
const oldCommands = existing[0].commands;
const isSameCommands = Object.keys(commands).every(
(key) => commands[key] === oldCommands[key]
);
if (isSameCommands) {
console.log(`🟡 Skipped existing PID+Version: ${pid} ${version}`);
continue;
}
const newCommands = {};
for (const key of Object.keys(commands)) {
if (commands[key] === true && oldCommands[key] === false) {
newCommands[key] = true;
}
}
if (Object.keys(newCommands).length === 0) {
console.log(`🟠 No new true commands → skip ${pid} ${version}`);
continue;
}
const newRelevantLog = extractRelevantLog(block, newCommands);
const updatedCommands = { ...oldCommands, ...newCommands };
const updatedLog = `${existing[0].log}\n[${filename}]\n${newRelevantLog}`;
await db.query(
`UPDATE pid_logs SET log = ?, commands = ?, errors = ? WHERE pid = ? AND version = ?`,
[
updatedLog,
JSON.stringify(updatedCommands),
`${existing[0].errors || ''}\n${errors.join('\n')}`.trim(),
pid,
version,
]
);
console.log(`🟢 Updated with new command(s) for ${pid} ${version}`);
}
} catch (err) {
console.error(`❌ Error ${filename}: ${err.message} ${err.stack}`);
}
}
}
// === Run crawl on start ===
// crawlLogs();
app.listen(PORT, () =>
console.log(`Server running at http://localhost:${PORT}`)
);