Cómo registrar todas las llamadas realizadas a una API

Introducción

Registrar llamadas a una API exige definir los intentos, la trazabilidad y la privacidad que necesita la integración. No basta con guardar un mensaje cuando algo falla: también hay que reconocer qué operación se intentó, si obtuvo respuesta y cómo se relaciona con el trabajo empresarial que la originó.

Una consulta lógica puede provocar varias peticiones por reintentos, redirecciones o renovación de acceso. Al mismo tiempo, una respuesta HTTP correcta puede contener un resultado que el proceso no puede utilizar. Mezclar todos estos acontecimientos bajo una única etiqueta de “llamada correcta” dificulta diagnosticar incidencias y medir consumo.

Esta guía explica cómo diseñar un registro de llamadas salientes, qué campos conservar, cómo instrumentar un cliente y cómo comprobar su cobertura. El objetivo es obtener evidencias útiles sin convertir los logs en una copia desprotegida de clientes, documentos y credenciales.

Índice

Qué significa realmente registrar todas las llamadas

La palabra “todas” necesita un perímetro. Puedes registrar los intentos que atraviesan un cliente controlado, pero no afirmar que cubres automáticamente todas las comunicaciones de una empresa. También puede haber conectores, plugins, tareas antiguas y peticiones realizadas por herramientas de terceros.

Inventariar los puntos de salida

Localiza dónde se generan llamadas: scripts programados, aplicaciones web, procesos manuales, servicios de sincronización y herramientas de automatización. Para cada punto, anota qué cliente utiliza y si dispone de instrumentación propia. Identifica además si hay reintentos dentro de una biblioteca o de un proxy.

Un registro de acceso de tu servidor web suele describir solicitudes que llegan a ese servidor; no es un inventario completo de sus peticiones salientes. Tampoco el panel del proveedor siempre conserva la información suficiente para relacionar cada solicitud con una tarea interna.

Definir una cobertura comprobable

Una formulación útil sería: “se registran todos los intentos HTTP iniciados por el módulo de sincronización de catálogo, sin muestreo”. Esa afirmación puede probarse. “Tenemos todos los logs” no permite saber qué procesos quedan fuera ni qué eventos se pierden al fallar el registro.

El inventario puede formar parte de la documentación de la infraestructura tecnológica. Mantén separados los componentes instrumentados, los pendientes y las limitaciones conocidas.

Incluso dentro del perímetro, una caída abrupta del proceso puede impedir escribir el cierre de un intento. Diseña el registro para representar esa incertidumbre en lugar de prometer una exhaustividad que no has demostrado.

Distinguir trabajo, operación, intento y resultado

Propongo utilizar cuatro unidades. Un trabajo agrupa una ejecución, como la sincronización de una jornada. Una operación representa una intención concreta, como consultar una página. Un intento es una petición HTTP específica. El resultado empresarial indica qué ocurrió finalmente con los datos.

Identificadores propuestos para no mezclar niveles
Nivel Ejemplo conceptual Identificador
Trabajo Actualización programada del catálogo. job_id
Operación Obtener la segunda página de productos. operation_id
Intento Primera petición y posterior repetición autorizada. attempt_id y número de intento
Resultado Página validada y almacenada, o enviada a revisión. Referencia al estado de procesamiento

Una operación mantiene su identificador durante sus reintentos; cada intento recibe uno nuevo. Si se crea una operación distinta, no reutilices el identificador solo porque llama a la misma ruta. Así podrás calcular tanto el número de peticiones como el número de tareas resueltas.

Las convenciones HTTP de OpenTelemetry contemplan intentos y reenvíos, incluidas redirecciones, y recomiendan representar esos hechos de manera coherente. Los nombres de campos de la tabla son una propuesta de registro local, no una reproducción literal de todo ese estándar.

El identificador de correlación no debería contener un correo, un número fiscal ni el nombre del cliente. Utiliza un valor opaco. Cuando necesites enlazarlo con un registro empresarial, conserva la correspondencia en un sistema con los permisos adecuados.

Seleccionar campos que ayuden a reconstruir lo ocurrido

El registro debe responder qué componente llamó, qué operación intentó, cuándo ocurrió, cuánto duró y qué tipo de resultado obtuvo. Diseña el esquema antes de añadir mensajes de depuración; así podrás procesarlo sin depender de frases libres.

Esquema mínimo orientativo para cada intento
Campo Uso Precaución
timestamp Ordenar eventos en el tiempo. Guardar zona horaria, preferiblemente UTC.
provider y operation Agrupar por servicio y función. Usar etiquetas estables, no URLs con datos.
operation_id y attempt_id Relacionar reintentos y eventos de inicio/cierre. No codificar identidad personal.
method y http_status Interpretar comunicación HTTP. El estado será nulo si no se recibió.
duration_ms Medir duración del intento. Documentar el tramo medido.
outcome Separar respuesta, timeout y otros fallos. Categorías controladas.
schema_version Mantener compatibilidad del registro. Versionar cambios de significado.

Puede añadirse el entorno, la versión del cliente o un identificador de petición devuelto por el proveedor. Este último es un dato externo: valida su formato y longitud antes de almacenarlo. No copies todas las cabeceras “por si acaso”.

Para duración utiliza un reloj apropiado para medir intervalos, y para la marca temporal un reloj de calendario con zona. Son dos finalidades diferentes. Documenta si el tiempo incluye espera en cola, conexión, recepción de cabeceras y descarga del cuerpo.

Si una petición falla antes de recibir HTTP, conserva null como estado y una categoría de transporte. Escribir “HTTP 0” puede confundir herramientas y personas: no es un código de estado recibido del proveedor.

Registrar contexto sin copiar secretos ni datos de negocio

La lista de campos permitidos es una defensa más clara que intentar borrar secretos de un volcado completo. Si solo guardas proveedor, operación, identificadores opacos, estado y duración, reduces de origen la posibilidad de exponer contenido.

OWASP recomienda excluir o proteger especialmente contraseñas, tokens y otros datos sensibles. En una integración, esto afecta a cabeceras de autorización, cookies, parámetros con claves y cuerpos que contienen información de clientes.

Las URLs también pueden contener información sensible

Una dirección completa puede incluir filtros con correos, códigos de cliente o secretos heredados en la consulta. Incluso la ruta puede revelar una referencia comercial. Para agrupar actividad, utiliza una etiqueta como customers.lookup o una plantilla controlada, no la dirección original de cada petición.

La misma cautela se aplica a mensajes de excepción. Un error de biblioteca puede incorporar la URL o parte de la solicitud. El registro general debería almacenar una clasificación y conservar los detalles adicionales solo cuando exista una necesidad justificada y un canal adecuado.

No confundir seudonimización con anonimato

Un identificador opaco permite reducir exposición, pero puede seguir siendo relacionable con una persona mediante otra tabla. Trátalo según esa posibilidad. Evita afirmar que una base de logs es anónima solo porque has sustituido nombres por códigos.

Para depuraciones excepcionales, define un alcance temporal, acceso restringido y un conjunto mínimo de datos. La política debe encajar con la protección de privacidad en los servicios digitales, no quedar suspendida cada vez que algo falla.

Utilizar registros estructurados y categorías estables

Un formato JSON por línea permite que cada evento sea independiente y pueda filtrarse mediante herramientas sencillas. No necesitas una plataforma compleja para empezar, pero sí una estructura que puedas conservar cuando cambie la forma de analizarla.

Este es un evento ficticio de cierre. No representa una respuesta empresarial completa ni contiene datos reales.

{
  "schema_version": 1,
  "event": "attempt_finished",
  "provider": "gestion",
  "operation": "products.list",
  "operation_id": "1a422689-3ac0-42b2-8597-40b869f816d1",
  "attempt_id": "4494b147-22f7-4b6a-9115-eb65a5965e1f",
  "attempt": 2,
  "timestamp": "2026-09-01T08:30:15+00:00",
  "method": "GET",
  "http_status": 200,
  "outcome": "http_response",
  "duration_ms": 284.6,
  "decoded_bytes": 4832
}

http_response significa que terminó la recepción prevista, no que los datos hayan superado validación ni que se hayan guardado. El resultado funcional debe registrarse en otro evento o estado, relacionado mediante operation_id.

Evitar esquemas que cambian sin avisar

No utilices duration en segundos en un programa y milisegundos en otro. Tampoco mezcles un código numérico con textos libres en la misma columna. Define tipos y significados y añade una versión cuando cambien de forma incompatible.

Limita las etiquetas de agrupación a un conjunto manejable. Una operación distinta por cada cliente convierte un indicador en miles de series poco útiles. Los detalles singulares pertenecen a las referencias de evento, no a cada etiqueta del panel.

Ejemplo en Python: inicio y cierre de cada intento

El siguiente ejemplo registra lecturas GET realizadas a través de una función concreta. Está pensado para una ejecución secuencial con Python 3.11 o posterior y Requests. El directorio del fichero debe existir y tener permisos adecuados; sitúalo fuera de cualquier directorio publicado por un servidor web.

Se escribe un evento de inicio antes de llamar y uno de cierre en finally. No se registran URL, cabeceras ni cuerpos. La rotación limita el tamaño de los archivos, pero no implementa una auditoría transaccional ni garantiza conservación durante un número concreto de días.

from __future__ import annotations

import json
import logging
import re
import time
from datetime import datetime, timezone
from logging.handlers import RotatingFileHandler
from uuid import UUID, uuid4

import requests


def configure_log(path: str) -> logging.Logger:
    logger = logging.getLogger('integration.http.attempts')
    logger.setLevel(logging.INFO)
    logger.propagate = False
    for handler in list(logger.handlers):
        handler.close()
        logger.removeHandler(handler)
    handler = RotatingFileHandler(
        path, maxBytes=2_000_000, backupCount=5, encoding='utf-8'
    )
    handler.setFormatter(logging.Formatter('%(message)s'))
    logger.addHandler(handler)
    return logger


def recorded_get(session: requests.Session, logger: logging.Logger,
                 url: str, *, provider: str, operation: str,
                 operation_id: UUID, attempt: int = 1,
                 max_bytes: int = 1_048_576) -> tuple[int, bytes]:
    # Etiquetas definidas por la aplicación; no nombres de clientes.
    for value in (provider, operation):
        if not re.fullmatch(r'[a-zA-Z0-9_.-]{1,80}', value):
            raise ValueError('Etiqueta no válida')
    if not isinstance(operation_id, UUID):
        raise ValueError('operation_id debe ser un UUID')
    if (type(attempt) is not int or attempt < 1
            or type(max_bytes) is not int or max_bytes < 1):
        raise ValueError('Límites no válidos')

    base = {
        'schema_version': 1,
        'provider': provider,
        'operation': operation,
        'operation_id': str(operation_id),
        'attempt_id': str(uuid4()),
        'attempt': attempt,
        'method': 'GET',
    }

    def emit(event: str, **fields) -> None:
        record = dict(base, event=event,
                      timestamp=datetime.now(timezone.utc).isoformat(),
                      **fields)
        logger.info(json.dumps(record, ensure_ascii=True, allow_nan=False))

    emit('attempt_started')
    start = time.perf_counter_ns()
    status = None
    outcome = 'client_error'
    received = 0
    try:
        with session.get(url, timeout=(3, 15), allow_redirects=False,
                         stream=True) as response:
            status = response.status_code
            body = bytearray()
            for chunk in response.iter_content(chunk_size=8192):
                body.extend(chunk)
                received = len(body)
                if received > max_bytes:
                    outcome = 'response_limit'
                    raise ValueError('Respuesta superior al límite configurado')
            outcome = 'http_response'
            return status, bytes(body)
    except requests.exceptions.SSLError:
        outcome = 'tls_error'
        raise
    except requests.exceptions.Timeout:
        outcome = 'timeout'
        raise
    except requests.exceptions.ConnectionError:
        outcome = 'connection_error'
        raise
    except requests.RequestException:
        outcome = 'transport_error'
        raise
    finally:
        emit('attempt_finished', http_status=status, outcome=outcome,
             duration_ms=round((time.perf_counter_ns() - start) / 1_000_000, 3),
             decoded_bytes=received)

La documentación de los manejadores de logging de Python describe la rotación por tamaño. El ejemplo mantiene un archivo activo y hasta cinco copias. Configura permisos, copias y tratamiento de fallos del registro de acuerdo con el entorno donde lo utilices.

El campo decoded_bytes cuenta los bytes del cuerpo entregados a la aplicación en este recorrido. No mide exactamente el tráfico de red, que incluye cabeceras y puede utilizar compresión. Las excepciones se propagan al llamador; este tampoco debe imprimirlas sin revisar si contienen información sensible.

Conectar la instrumentación con un cliente controlado

La función anterior solo representa correctamente los intentos visibles si la sesión no esconde otras llamadas. Para este ejemplo, configura explícitamente cero reintentos de transporte y no sigas redirecciones. Si después introduces reintentos, realiza cada uno a través de la misma función, con el mismo identificador de operación y un número de intento creciente.

El dominio de este fragmento es ficticio. Sirve para mostrar la conexión entre componentes, no para ejecutarlo contra un servicio real sin adaptar su contrato.

import os
from uuid import uuid4
import requests
from requests.adapters import HTTPAdapter
from api_logging import configure_log, recorded_get

logger = configure_log('/ruta/privada/api_calls.jsonl')
operation_id = uuid4()

with requests.Session() as session:
    session.trust_env = False
    session.mount('https://', HTTPAdapter(max_retries=0))
    session.headers.update({
        'Authorization': 'Bearer ' + os.environ['API_TOKEN'],
        'Accept': 'application/json',
    })
    status, body = recorded_get(
        session, logger, 'https://gestion.example/v1/products',
        provider='gestion', operation='products.list',
        operation_id=operation_id,
    )
    # Validar HTTP, formato y contrato antes de utilizar body.
    # No volcar body ni la excepción completa al log general.

Una autenticación con varios pasos, un SDK externo o un proxy pueden añadir tráfico fuera de esta función. En ese caso necesitas instrumentar el nivel apropiado o declarar esa limitación. El ejemplo no intercepta automáticamente otras bibliotecas del proceso.

Los tiempos de espera de Requests tampoco representan un plazo total de ejecución; su documentación de timeouts explica el alcance. Si se cancela el proceso abruptamente, el evento de cierre puede no escribirse aunque exista uno de inicio.

Comprobar que el registro cubre lo que afirma cubrir

La cobertura debe probarse con un transporte simulado o un servidor de ensayo. Genera una cantidad conocida de operaciones e intenta reconciliar sus eventos. Una prueba de cobertura no depende de que el proveedor real esté disponible.

Probar respuestas y fallos

Incluye una respuesta 200, otra de error HTTP, un timeout y un cuerpo que supere el límite. Comprueba que cada intento visible produce su inicio y su cierre cuando el proceso termina normalmente. Verifica que los intentos sin estado HTTP conservan el valor nulo.

Añade una operación que falle una vez y se repita. Debes obtener dos identificadores de intento y un único identificador de operación. Si solo aparece el segundo intento, tienes una pérdida de detalle. Si aparecen dos operaciones independientes, has perdido su relación.

Buscar fugas de información

Introduce en las respuestas simuladas un token de prueba, un correo ficticio y caracteres de salto de línea. Comprueba que ninguno se copia al registro. La prueba debe inspeccionar el fichero real, no solo asumir que los desarrolladores respetarán la política.

También revisa los logs del ejecutor, del SDK y de las herramientas de depuración. Una función prudente puede coexistir con otra capa que imprime solicitudes completas.

Interpretar inicios sin cierre

Un inicio pendiente puede indicar ejecución en curso, interrupción, fallo de escritura o evento que todavía no ha llegado al colector. No lo conviertas automáticamente en una petición fallida. Primero comprueba estado del trabajo y retraso de entrega. Esta distinción permite investigar incertidumbre sin inventar un resultado.

Diseñar almacenamiento, conservación y acceso

El volumen depende de los intentos, los eventos por intento y el tamaño medio de cada evento. Como ejemplo hipotético, 10.000 intentos diarios, dos eventos por intento y 700 bytes por evento generan aproximadamente 14 MB diarios antes de compresión y otros gastos. Esa estimación permite planificar, no fija una necesidad universal.

Separar rotación de conservación

Rotar por tamaño evita archivos indefinidamente grandes, pero un aumento de actividad puede hacer que el histórico disponible cubra menos días. Define el periodo que necesitas para investigar incidencias y contrástalo con las obligaciones aplicables a tus datos y contratos; no adoptes una duración legal genérica.

Si necesitas conservar más detalle, puedes enviar eventos a un repositorio central. Introduce entonces controles de transporte, permisos, capacidad y pérdida. Un colector no elimina automáticamente el riesgo de que un evento nunca llegue.

Evitar escrituras concurrentes mal coordinadas

El ejemplo está pensado para un proceso. Si varios procesos escriben y rotan el mismo archivo, necesitas una arquitectura de registro adecuada. El recetario oficial de logging de Python desarrolla alternativas de coordinación, como colas y receptores específicos.

La lectura de logs también debe controlarse. No concedas acceso general a un proveedor externo solo porque “son datos técnicos”. Para investigar un caso, una exportación mínima y revisada puede ser suficiente.

La gestión del histórico se puede relacionar con conservar históricos empresariales sin perder trazabilidad.

Convertir eventos en indicadores sin mezclar denominadores

Un registro puede responder cuántos intentos se realizaron, qué operaciones generaron más repeticiones y qué proveedor concentra demoras. Antes de calcular porcentajes, define el denominador.

Supongamos un escenario ficticio con 100 operaciones finalmente resueltas mediante 120 intentos: 20 primeros intentos fallaron y sus repeticiones tuvieron éxito. La proporción de operaciones resueltas es del 100 %, mientras que la de intentos con respuesta satisfactoria es aproximadamente del 83,3 %. Ambas describen aspectos distintos.

Medir el coste de la recuperación

La relación entre intentos y operaciones es 1,2 en ese ejemplo. Si aumenta, puede indicar que los reintentos mantienen el resultado aparente a costa de más consumo y retraso. Una integración que termina “bien” puede estar degradándose.

Para medir duración, distingue el intento individual del tiempo que el trabajo pasa en cola y entre repeticiones. Un proveedor puede responder rápido mientras el sistema tarda mucho en completar la tarea por una política de reprogramación excesiva.

Detectar ausencia de actividad esperada

La ausencia de errores no demuestra actividad. Comprueba si debía haber un trabajo y si existen eventos asociados. Si no se ejecutó, no habrá una petición fallida que contar.

Para un panel inicial, propongo volumen de intentos, operaciones resueltas, repeticiones por operación, pendientes antiguos y cobertura de eventos. El enfoque debe conectarse con reporting empresarial práctico, utilizando indicadores que lleven a decisiones y no solo a gráficos.

Diferenciar observabilidad de auditoría y conciliación

El registro operativo ayuda a entender lo ocurrido. Una auditoría que exige evidencia duradera necesita garantías adicionales sobre persistencia, acceso y alteraciones. No son el mismo problema y no conviene atribuir a un archivo rotado propiedades que no posee.

El intervalo entre escribir y llamar

Si registras primero y después haces la petición, un fallo puede dejar una intención sin llamada. Si llamas primero y registras después, otro fallo puede dejar una llamada sin evidencia local. Un registro corriente no hace atómicos esos dos hechos.

Para operaciones críticas, separa un diario duradero de intenciones y estados de los logs de diagnóstico. Persiste la intención antes del envío cuando el diseño lo requiera y concilia las operaciones de resultado desconocido con el destino. Esto sigue sin convertir una red en una transacción distribuida automática.

Decidir qué ocurre cuando falla el registro

Una consulta auxiliar puede continuar con una alarma de pérdida de observabilidad. Una operación que no debe ejecutarse sin evidencia previa puede necesitar bloquearse antes del envío. Esa decisión debe tomarla el diseño funcional y técnico, no el comportamiento accidental de un manejador de logs.

El ejemplo de Python no implementa esa garantía de auditoría: es instrumentación operativa. Si tu requisito exige persistencia confirmada, utiliza un mecanismo apropiado y prueba sus fallos.

Por último, concilia los resultados relevantes con el sistema de destino. Un log que dice “enviado” no demuestra por sí solo que un pedido existe con los datos correctos.

Preguntas frecuentes

¿Debo guardar el cuerpo completo de cada petición y respuesta?

No como comportamiento general. Conserva los metadatos necesarios y evita copiar credenciales o información empresarial. Cuando una depuración excepcional requiera contenido, delimita datos, acceso y duración, y revisa también las copias que se generan.

¿Un log del servidor web registra mis llamadas salientes?

No necesariamente. Debes revisar qué componente lo genera y qué tráfico observa. Para llamadas salientes desde scripts o plugins, instrumenta el cliente o una capa que realmente vea esas peticiones.

¿Conviene registrar solo las peticiones fallidas?

Eso no permite conocer el volumen total ni reconstruir la cobertura. Para un registro exhaustivo dentro del perímetro definido, conserva también los intentos sin error y separa el detalle necesario del nivel de depuración.

¿Puedo usar muestreo y afirmar que registro todas las llamadas?

No respecto al conjunto muestreado. Puedes conservar todas las evidencias mínimas en un canal y muestrear trazas detalladas en otro, pero debes explicar qué información está completa y cuál es solo una muestra.

¿Un evento de inicio sin cierre demuestra que la API falló?

No. También puede deberse a una interrupción local, un problema del registro o un evento pendiente de entrega. Es un caso que requiere correlación y, cuando afecta a una escritura, conciliación del resultado.

¿El ejemplo garantiza una auditoría completa?

No. Registra intentos de lectura que pasan por la función y limita qué datos escribe. Una auditoría con garantías de persistencia, integridad y recuperación exige un diseño adicional y pruebas específicas.

Conclusión

Registrar llamadas a una API de forma útil empieza por definir el perímetro y distinguir operaciones, intentos y resultados. Después hay que instrumentar los puntos de salida, conservar un esquema estable y comprobar que no se filtran datos que nunca deberían estar en los logs.

La calidad del registro no se mide por cuánto contenido acumula, sino por las preguntas que permite responder y las limitaciones que deja visibles. Un sistema que reconoce sus eventos pendientes y concilia resultados ofrece más control que otro que declara éxito ante cualquier respuesta.

Aprender observabilidad y diagnóstico de integraciones

La trazabilidad de APIs reúne programación, análisis de eventos, seguridad y comprensión de procesos. Para avanzar de forma estructurada en estas materias, consulta los programas de formación de ESTUDIO METADATOS, basados en cursos y másteres online, y revisa cuáles se ajustan a tus objetivos de aprendizaje.

Ver programas de formación relacionados

Written by