| const fs = require('fs') |
| const path = require('path') |
|
|
| const logsDir = path.join(__dirname, '..', 'health-logs') |
|
|
| const CATEGORIES = ['timeout', 'structural', 'blocked', 'rate_limited', 'not_found', 'unknown'] |
|
|
| async function readProviderLogs(providerName, hoursBack = 12) { |
| const filePath = path.join(logsDir, `${providerName}.jsonl`) |
| if (!fs.existsSync(filePath)) return [] |
|
|
| const cutoff = Date.now() - hoursBack * 60 * 60 * 1000 |
| const content = await fs.promises.readFile(filePath, 'utf-8') |
| if (!content.trim()) return [] |
|
|
| return content.split('\n') |
| .map(line => { |
| try { return JSON.parse(line) } catch { return null } |
| }) |
| .filter(Boolean) |
| .filter(entry => new Date(entry.timestamp).getTime() >= cutoff) |
| } |
|
|
| function classify(logs) { |
| const counts = {} |
| const samples = {} |
|
|
| for (const cat of CATEGORIES) { |
| counts[cat] = 0 |
| samples[cat] = [] |
| } |
|
|
| for (const log of logs) { |
| const cat = CATEGORIES.includes(log.category) ? log.category : 'unknown' |
| counts[cat]++ |
| if (samples[cat].length < 3) { |
| samples[cat].push(log) |
| } |
| } |
|
|
| let dominantCategory = 'unknown' |
| let maxCount = 0 |
| for (const cat of CATEGORIES) { |
| if (counts[cat] > maxCount) { |
| maxCount = counts[cat] |
| dominantCategory = cat |
| } |
| } |
|
|
| return { |
| total: logs.length, |
| dominantCategory, |
| counts, |
| samples, |
| timeWindow: { |
| from: logs.length > 0 ? logs[0].timestamp : null, |
| to: logs.length > 0 ? logs[logs.length - 1].timestamp : null, |
| }, |
| } |
| } |
|
|
| async function classifyAll(hoursBack = 12) { |
| if (!fs.existsSync(logsDir)) return {} |
|
|
| const files = fs.readdirSync(logsDir).filter(f => f.endsWith('.jsonl')) |
| const result = {} |
|
|
| for (const file of files) { |
| const providerName = file.replace('.jsonl', '') |
| const logs = await readProviderLogs(providerName, hoursBack) |
| if (logs.length === 0) continue |
| result[providerName] = classify(logs) |
| } |
|
|
| return result |
| } |
|
|
| function classifySingle(error) { |
| if (!error) return 'unknown' |
| const msg = (error.message || String(error)).toLowerCase() |
|
|
| if (msg.includes('timeout') || msg.includes('excedi贸') || msg.includes('exceeded')) return 'timeout' |
| if (msg.includes('403') || msg.includes('forbidden')) return 'blocked' |
| if (msg.includes('429') || msg.includes('too many requests')) return 'rate_limited' |
| if (msg.includes('cannot read') || msg.includes('undefined') || msg.includes('selector') || msg.includes('cheerio')) return 'structural' |
|
|
| |
| |
| if (msg.includes('404') || msg.includes('not found')) { |
| if (/\/(ver-|pelicula|episodio|serie|movie|_next\/data|search\.json)/i.test(msg)) return 'structural' |
| return 'not_found' |
| } |
| if (/no se encontr贸|no encontrad[ao]/i.test(msg)) return 'not_found' |
|
|
| return 'unknown' |
| } |
|
|
| module.exports = { |
| readProviderLogs, |
| classify, |
| classifyAll, |
| classifySingle, |
| CATEGORIES, |
| } |
|
|