fix(cdp_collect_console): cap maxEntries + descarta backlog previo a la ventana
CdpCollectConsole gana un parametro maxEntries (default 200): al alcanzarlo deja de acumular y marca una ConsoleEntry final '_truncated', evitando reventar la salida en paginas verbosas. Ademas descarta los eventos console anteriores al inicio de la captura (backlog acumulado en la conexion CDP viva), capturando solo lo emitido dentro de la ventana durationMs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,11 +22,33 @@ type ConsoleEntry struct {
|
||||
Timestamp float64 `json:"timestamp"` // CDP timestamp (monotonic seconds) o wall time
|
||||
}
|
||||
|
||||
// consoleCollectDefaultMax es el tope de entradas por defecto cuando el caller
|
||||
// pasa maxEntries <= 0. Acota la salida en paginas verbosas (setInterval ruidoso,
|
||||
// SPA que loguea sin parar) para no devolver cientos de entradas y reventar el
|
||||
// output del tool.
|
||||
const consoleCollectDefaultMax = 200
|
||||
|
||||
// CdpCollectConsole habilita los dominios Runtime y Log en la conexion, se
|
||||
// suscribe a los eventos de consola/excepcion/log del navegador y acumula todo
|
||||
// lo que ocurra durante `durationMs` milisegundos. Es un SNAPSHOT temporal:
|
||||
// captura solo lo emitido dentro de la ventana, no el historico previo de la
|
||||
// pagina. Si durationMs <= 0 usa 1500ms por defecto.
|
||||
// lo que ocurra durante `durationMs` milisegundos, hasta un maximo de
|
||||
// `maxEntries` entradas. Es un SNAPSHOT temporal: captura solo lo emitido dentro
|
||||
// de la ventana, no el historico previo de la pagina. Si durationMs <= 0 usa
|
||||
// 1500ms por defecto; si maxEntries <= 0 usa 200 por defecto.
|
||||
//
|
||||
// Dos defensas contra el backlog de una conexion del pool que lleva rato abierta
|
||||
// con Runtime habilitado (donde Runtime.enable flushea consoleAPICalled rezagados
|
||||
// con timestamps antiguos, y un setInterval verboso puede inundar):
|
||||
// - Filtro por timestamp: se captura `startMs` (wall time, ms epoch) JUSTO antes
|
||||
// de habilitar los dominios y solo se acumulan eventos cuyo timestamp sea >=
|
||||
// startMs. Los eventos `consoleAPICalled`/`exceptionThrown`/`Log.entryAdded`
|
||||
// traen `timestamp` en ms epoch, asi que los rezagados del flush (anteriores
|
||||
// a startMs) se descartan. Eventos sin timestamp (0) se aceptan: no hay forma
|
||||
// de fecharlos y casi siempre son nuevos.
|
||||
// - Cap por cantidad: alcanzado `maxEntries` se dejan de acumular entradas, pero
|
||||
// la funcion NO corta la ventana — sigue durmiendo hasta `durationMs` para no
|
||||
// dejar los dominios CDP en estado raro (handlers a medio drenar). Las entradas
|
||||
// posteriores al cap simplemente se descartan; el flag de truncamiento se
|
||||
// refleja como una ConsoleEntry final de Type "_truncated".
|
||||
//
|
||||
// Eventos capturados y como se mapean a ConsoleEntry.Type:
|
||||
// - Runtime.consoleAPICalled -> el `type` del evento (log/info/warning/error/...)
|
||||
@@ -35,19 +57,45 @@ type ConsoleEntry struct {
|
||||
//
|
||||
// Robusta ante silencio: si no llega ningun evento devuelve un slice vacio
|
||||
// (no nil, no error). La conexion debe estar abierta; la funcion no la cierra.
|
||||
func CdpCollectConsole(c *CDPConn, durationMs int) ([]ConsoleEntry, error) {
|
||||
func CdpCollectConsole(c *CDPConn, durationMs int, maxEntries int) ([]ConsoleEntry, error) {
|
||||
if c == nil {
|
||||
return nil, fmt.Errorf("cdp collect console: conexion nula")
|
||||
}
|
||||
if durationMs <= 0 {
|
||||
durationMs = 1500
|
||||
}
|
||||
if maxEntries <= 0 {
|
||||
maxEntries = consoleCollectDefaultMax
|
||||
}
|
||||
|
||||
// startMs marca el inicio de la ventana en ms epoch (mismo dominio que el
|
||||
// `timestamp` de los eventos CDP). Eventos anteriores = backlog -> se descartan.
|
||||
startMs := float64(time.Now().UnixMilli())
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
entries = make([]ConsoleEntry, 0, 16)
|
||||
mu sync.Mutex
|
||||
entries = make([]ConsoleEntry, 0, 16)
|
||||
truncated bool
|
||||
)
|
||||
|
||||
// add intenta acumular una entrada respetando el filtro por timestamp y el cap.
|
||||
// Devuelve sin hacer nada si la entrada es backlog o si ya se alcanzo el tope.
|
||||
add := func(e ConsoleEntry) {
|
||||
// Descartar backlog: eventos fechados antes del inicio de la ventana.
|
||||
// Timestamp 0 (sin fecha) se acepta — no se puede clasificar como viejo.
|
||||
if e.Timestamp != 0 && e.Timestamp < startMs {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
if len(entries) >= maxEntries {
|
||||
truncated = true
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
entries = append(entries, e)
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// Helpers para extraer campos de map[string]any sin pelearse con cast.
|
||||
str := func(m map[string]any, k string) string {
|
||||
if v, ok := m[k]; ok {
|
||||
@@ -117,9 +165,7 @@ func CdpCollectConsole(c *CDPConn, durationMs int) ([]ConsoleEntry, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
mu.Lock()
|
||||
entries = append(entries, entry)
|
||||
mu.Unlock()
|
||||
add(entry)
|
||||
})
|
||||
defer cancel1()
|
||||
|
||||
@@ -168,9 +214,7 @@ func CdpCollectConsole(c *CDPConn, durationMs int) ([]ConsoleEntry, error) {
|
||||
if entry.Text == "" {
|
||||
entry.Text = "uncaught exception"
|
||||
}
|
||||
mu.Lock()
|
||||
entries = append(entries, entry)
|
||||
mu.Unlock()
|
||||
add(entry)
|
||||
})
|
||||
defer cancel2()
|
||||
|
||||
@@ -180,16 +224,23 @@ func CdpCollectConsole(c *CDPConn, durationMs int) ([]ConsoleEntry, error) {
|
||||
if le == nil {
|
||||
return
|
||||
}
|
||||
// Log.entryAdded reporta `timestamp` en segundos epoch (a diferencia de
|
||||
// consoleAPICalled/exceptionThrown que lo dan en ms). Normalizar a ms para
|
||||
// que el filtro por startMs compare en el mismo dominio. Heurística: si el
|
||||
// valor parece segundos (varios órdenes por debajo de un ms epoch actual),
|
||||
// multiplicar por 1000.
|
||||
ts := num(le, "timestamp")
|
||||
if ts > 0 && ts < startMs/100 {
|
||||
ts *= 1000
|
||||
}
|
||||
entry := ConsoleEntry{
|
||||
Type: str(le, "level"), // verbose|info|warning|error
|
||||
Text: str(le, "text"),
|
||||
URL: str(le, "url"),
|
||||
Line: int(num(le, "lineNumber")),
|
||||
Timestamp: num(le, "timestamp"),
|
||||
Timestamp: ts,
|
||||
}
|
||||
mu.Lock()
|
||||
entries = append(entries, entry)
|
||||
mu.Unlock()
|
||||
add(entry)
|
||||
})
|
||||
defer cancel3()
|
||||
|
||||
@@ -207,12 +258,24 @@ func CdpCollectConsole(c *CDPConn, durationMs int) ([]ConsoleEntry, error) {
|
||||
// dependen de consoleAPICalled. Solo cerramos Log que abrimos aqui.
|
||||
defer c.sendCDP("Log.disable", nil)
|
||||
|
||||
// Ventana de captura.
|
||||
// Ventana de captura. No hacemos early-return al alcanzar el cap: seguimos
|
||||
// durmiendo la ventana completa para no dejar los dominios CDP a medio drenar.
|
||||
time.Sleep(time.Duration(durationMs) * time.Millisecond)
|
||||
|
||||
mu.Lock()
|
||||
out := make([]ConsoleEntry, len(entries))
|
||||
copy(out, entries)
|
||||
wasTruncated := truncated
|
||||
mu.Unlock()
|
||||
|
||||
// Senal de truncamiento limpia: una entrada final que el caller puede detectar
|
||||
// por Type == "_truncated" sin cambiar la forma del slice.
|
||||
if wasTruncated {
|
||||
out = append(out, ConsoleEntry{
|
||||
Type: "_truncated",
|
||||
Text: fmt.Sprintf("output truncado al alcanzar maxEntries=%d; entradas posteriores descartadas", maxEntries),
|
||||
Timestamp: float64(time.Now().UnixMilli()),
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user