Ctrl K
ComunidadGuías…

scripts/_portwatch-request-diagnostics.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { randomUUID } from 'node:crypto';

const QUERY_FIELDS = [
  'where', 'outFields', 'orderByFields', 'resultOffset', 'resultRecordCount',
  'outStatistics', 'returnGeometry', 'outSR', 'f',
];
const RESPONSE_HEADERS = ['content-type', 'date', 'retry-after', 'x-esri-request-id', 'x-request-id', 'cf-ray'];
const MAX_ERROR_BODY_BYTES = 64 * 1024;

function safeText(value, maxBytes = 256) {
  const redacted = String(value ?? '')
    .replace(/(https?:\/\/)[^\s/]*@/gi, '$1[redacted]@')
    .replace(/\b(Bearer|Basic)\s+[^\s,;"']+/gi, '$1 [redacted]')
    .replace(/([?&])([^=\s?&#"']+)=([^\s&#"']*)/g, (match, separator, name) =>
      QUERY_FIELDS.includes(name) ? match : `${separator}${name}=[redacted]`)
    .replace(/(["']?(?:token|api[_-]?key|access_token|password|secret|signature)["']?\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;&}]+)/gi, '$1[redacted]');
  const buffer = Buffer.from(redacted);
  return buffer.length > maxBytes
    ? `${buffer.subarray(0, maxBytes).toString('utf8')} [truncated]` : redacted;
}

function errorCauses(error) {
  const causes = [];
  const pending = [{ error, parent: null }];
  const seen = new Set();
  while (pending.length && causes.length < 4) {
    const { error: current, parent } = pending.shift();
    if (current == null || seen.has(current)) continue;
    seen.add(current);
    const index = causes.length;
    const entry = {
      parent,
      name: safeText(current.name, 64),
      message: safeText(current.message ?? current),
      code: safeText(current.code, 64),
      stage: safeText(current.stage, 64),
    };
    if (current.proxyFailure) {
      entry.proxyFailure = {
        stage: safeText(current.proxyFailure.stage, 64),
        httpStatus: current.proxyFailure.httpStatus ?? null,
        proxyConnectStatus: current.proxyFailure.proxyConnectStatus ?? null,
      };
    }
    causes.push(entry);
    if (current.cause) pending.push({ error: current.cause, parent: index });
    if (Array.isArray(current.errors)) {
      for (const child of current.errors.slice(0, 3)) pending.push({ error: child, parent: index });
    }
  }
  return causes;
}

export function createPortwatchRequestTrace(url, route) {
  const started = Date.now();
  const requestId = randomUUID();
  let endpoint;
  const query = {};
  try {
    const parsed = new URL(url);
    endpoint = safeText(`${parsed.origin}${parsed.pathname}`);
    for (const name of QUERY_FIELDS) {
      if (parsed.searchParams.has(name)) query[name] = safeText(parsed.searchParams.get(name), 384);
    }
  } catch {
    endpoint = 'invalid-url';
  }

  return {
    fail(error, { phase, response, arcgisError } = {}) {
      try {
        if (arcgisError == null && Buffer.isBuffer(response?.buffer) && response.buffer.length <= MAX_ERROR_BODY_BYTES) {
          try {
            arcgisError = JSON.parse(response.buffer.toString('utf8'))?.error;
          } catch {
            arcgisError = undefined;
          }
        }
        const finished = Date.now();
        const headers = {};
        for (const name of RESPONSE_HEADERS) {
          const value = typeof response?.headers?.get === 'function'
            ? response.headers.get(name) : response?.headers?.[name];
          if (value != null) headers[name] = safeText(value, 128);
        }
        const record = {
          event: 'portwatch_request_failure',
          requestId, route, phase, endpoint, query,
          startedAt: new Date(started).toISOString(),
          finishedAt: new Date(finished).toISOString(),
          durationMs: Math.max(0, finished - started),
          status: response?.status ?? (error?.proxyConnect ? null : error?.status ?? null),
          headers,
          causes: errorCauses(error),
        };
        if (arcgisError != null) {
          record.arcgisError = {
            code: typeof arcgisError.code === 'number' ? arcgisError.code : safeText(arcgisError.code, 64),
            message: safeText(arcgisError.message),
            details: Array.isArray(arcgisError.details)
              ? arcgisError.details.slice(0, 4).map((detail) => safeText(detail)) : [],
          };
        }
        console.warn(JSON.stringify(record));
      } catch {
        return;
      }
    },
  };
}