Como instrumentar errores JS y reportarlos a un endpoint en WP en WordPress

Contents

Introducción

Este artículo explica, paso a paso y con todo detalle técnico, cómo instrumentar errores de JavaScript en un sitio WordPress y reportarlos a un endpoint en el propio WordPress. Verás la implementación front-end (captura y envío de errores), el backend en WordPress (ruta REST, validación, almacenamiento), consideraciones de seguridad, rendimiento, privacidad y cómo visualizar los registros desde el panel de administración.

Visión general

La idea general es:

  • Capturar errores JS y promesas no gestionadas en el cliente.
  • Enriquecer el evento con contexto (URL, navegador, user agent, usuario WP si aplica, tags personalizados).
  • Enviar los eventos al servidor mediante la REST API de WordPress o un endpoint admin-ajax/privado.
  • Validar y almacenar los eventos en la base de datos (tabla propia) o externalizar a un servicio de terceros.
  • Proveer un panel de administración para consultar y filtrar los eventos.

Recomendaciones iniciales

  • Uso de source maps: Sin source maps las trazas están ofuscadas/minificadas. Para una trazabilidad correcta, habilita source maps en producción (con protección) o sube las versiones map a un bucket privado o servicio de resolución de mapas.
  • Muestreo (sampling): No envíes el 100% de errores en sites de alto tráfico aplica sampling, por ejemplo 1% o 5% por tipo de error, con reglas para errores críticos.
  • Protección de datos y GDPR: No envíes datos personales (PII). Si es necesario, anonimiza (usuario_id en lugar de email) y documenta en la política de privacidad.
  • Rendimiento: Haz el envío en background, utiliza batching y backoff exponencial para reintentos.

Captura de errores en el cliente (JavaScript)

Debes capturar:

  • Errores sin capturar: window.addEventListener(error)
  • Promesas rechazadas no gestionadas: window.addEventListener(unhandledrejection)
  • Error global legacy: window.onerror (opcional, pero útil para navegadores antiguos)

Ejemplo de implementación client-side

Este script captura errores, construye una carga útil (payload) y la envía a la REST API de WordPress. Se incluyen batching, reintentos simples y protección de sampling.

// Código: instrumentación de errores en cliente
(function(){
  // Configuración inyectada por WP (localize_script)
  var ENDPOINT = window.__WP_ERROR_ENDPOINT  /wp-json/my-errors/v1/collect
  var NONCE = window.__WP_ERROR_NONCE  null
  var SAMPLE_RATE = (window.__WP_ERROR_SAMPLE_RATE  1) // 1 == 100%
  var BATCH_SIZE = 10
  var BATCH_INTERVAL = 5000 // ms

  var queue = []
  var sending = false

  function shouldSend() {
    return Math.random() < SAMPLE_RATE
  }

  function buildPayload(errData) {
    return {
      site_url: location.origin,
      page: location.href,
      referrer: document.referrer  null,
      ua: navigator.userAgent,
      timestamp: new Date().toISOString(),
      error: errData
    }
  }

  function enqueue(event) {
    queue.push(event)
    if (queue.length >= BATCH_SIZE) {
      flush()
    }
  }

  function flush() {
    if (sending  queue.length === 0) return
    sending = true
    var batch = queue.splice(0, BATCH_SIZE)
    var body = JSON.stringify({events: batch})
    var headers = {Content-Type:application/json}
    if (NONCE) headers[X-WP-Nonce] = NONCE

    fetch(ENDPOINT, {method: POST, body: body, headers: headers, credentials: same-origin})
      .then(function(res){
        sending = false
        if (!res.ok) {
          // Re-enqueue with simple retry/backoff (push front)
          queue = batch.concat(queue)
          // Optional: schedule retry
          setTimeout(flush, 5000)
        }
      }).catch(function(){
        sending = false
        // Put events back and retry later
        queue = batch.concat(queue)
        setTimeout(flush, 5000)
      })
  }

  // Handler para window.onerror
  window.onerror = function(message, source, lineno, colno, error) {
    try {
      if (!shouldSend()) return
      var err = {
        type: error,
        message: message,
        source: source,
        lineno: lineno,
        colno: colno,
        stack: error  error.stack ? String(error.stack) : null,
      }
      enqueue(buildPayload(err))
    } catch(e) {}
    // No bloquear ejecución
  }

  // Handler para promesas no gestionadas
  window.addEventListener(unhandledrejection, function(e) {
    try {
      if (!shouldSend()) return
      var reason = e  e.reason
      var err = {
        type: unhandledrejection,
        message: (reason  reason.message)  String(reason)  unknown,
        stack: (reason  reason.stack) ? String(reason.stack) : null
      }
      enqueue(buildPayload(err))
    } catch (ex) {}
  })

  // Handler de errores capturados por event listeners (p.ej. recursos)
  window.addEventListener(error, function(e) {
    try {
      if (!shouldSend()) return
      // event.target puede ser ,