File size: 3,001 Bytes
92a83a5
 
 
 
 
 
 
1ce7a42
92a83a5
 
 
 
1ce7a42
 
92a83a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1ce7a42
92a83a5
 
 
 
 
 
 
1ce7a42
92a83a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1ce7a42
 
 
 
 
 
 
 
92a83a5
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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,
}