apilink / utils /errorClassifier.js
franyer24's picture
update logic
1ce7a42 verified
Raw
History Blame Contribute Delete
3 kB
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'
// 404 con path de URL = structural (sitio cambi贸 de estructura)
// 404 sin path = not_found (contenido no existe en ese provider)
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,
}