Elastic APM en aplicaciones frontend (React en este artículo)

Elastic APM en aplicaciones frontend (React en este artículo)

Continuando con los artículos sobre monitoreo de aplicaciones, en el anterior escribía sobre Elastic APM y como se integra con el backend (mi ejemplo fue creado en nodejs), pues bien, esta vez utilizaremos Elastic APM pero en el frontend utilizando el paquete @elastic/apm-rum que no es más que otro agente de APM para aplicaciones front.

Qué es RUM (Real User Monitoring)?

Es la abreviatura de Monitoreo de Usuarios Reales o por sus siglas en inglés RUM, también se conoce como supervisión de la experiencia digital, medición de usuarios reales, supervisión de usuarios o supervisión de la experiencia del usuario final y es la práctica de usar datos de usuarios de una aplicación para monitorear y comprender el rendimiento de la aplicación, digamos que es un tipo de monitoreo pasivo que analiza los datos del usuario y su experiencia mientras navega por un sitio web.

Y para qué sirve RUM?

RUM rastrea métricas como el tiempo de DNS, el tiempo hasta el primer byte (time-to-first-byte), la puntuación APDEX, el tiempo de carga de la página completa, el tiempo que lleva cargar elementos específicos, solicitudes HTTP y bloqueos en el frontend.

Y como se hace la integración?

Empecemos con lo básico que es instalar el paquete de @elastic/apm-rum.

yarn add @elastic/apm-rum

Como buena práctica siempre es recomendable crear módulos o archivos separados que integran diferentes funcionalidades a nuestras aplicaciones, entonces yo recomiendo creen un archivo llamado monitor.js y peguen el código a contiuación.


import { init as RumAgent } from '@elastic/apm-rum';

const rum = RumAgent({
    serviceName: 'mi_aplicacion',
    serverUrl: 'http://localhost:8200'
});

export default rum;

Brevemente explico que significan los parámetros que he definido (y que son los parámetros básicos que se recomiendan definir):

  • serviceName: Es el nombre que se mostrará en el panel de administración de Elastic APM, es como el identificador de tu aplicación ante APM.
  • serverUrl: La dirección web del servidor de APM, en este caso lo he definido como localhost:8200 porque mi servidor está corriendo en un contenedor de Docker y que si no lo tienen, lo dejo al final de este artículo.

 La explicación de todos los parámetros la pueden encontrar desde la página de Elasticsearch en el proyecto APM RUM.

Y ahora lo siguiente simplemente es cargar el archivo monitor.js en nuestro index.js de React.


import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import rum from './monitor';

rum.setInitialPageLoadName('Landing Page');

ReactDOM.render(
  <react.strictmode>
    <app></app>
  </react.strictmode>,
  document.getElementById('root')
);

Ahora al abrir la aplicación de React en el navegador, estaríamos generando de tráfico y automáticamente se estarían enviando métricas hacia nuestro servidor de APM (usualmente en localhost estaria en http://localhost:5601/app/apm), puedes verificar abriendo en otra pestaña de tu navegador una instancia de Kibana y haciendo click sobre el módulo de APM, debería ver un servicio llamado "mi_aplicacion". Y que al hacer clic en el nombre del servicio, accederá a la página de transacciones. Debería ver la lista predeterminada de "page-load" y ver una para "Landing Page".

Lo que verás a continuación son métricas de la página que se ha cargado y eso comprende marcadores como timeToFirstByte, domInteractive, domComplete y firstContentfulPaint. Cuando haya un problema de rendimiento, éstas métricas nos permitirán decidir fácilmente si el problema se debe a servicios de backend lentos, una red lenta o simplemente un navegador de cliente lento.

Hay que tener en cuenta que si utilizas esta librería en frameworks modernos como React en este caso, estas métricas pueden representar solo la parte "estática" de la página web, debido a la naturaleza asincrónica de React y de que actualmente rutas o componentes se cargan bajo demanda.

Y por si no ha quedado claro que significan los marcadores que aparecen en el panel de seguimiento, les dejo una rápida explicación:

timeToFirstByte: Es la cantidad de tiempo que espera un navegador para recibir la primera información del servidor web después de solicitarla. Representa una combinación de velocidad de procesamiento de la red y del servidor.

domInteractive: Es el tiempo inmediatamente antes de que el agente de usuario establezca la preparación del documento actual en "interactive", lo que significa que el navegador ha terminado de analizar todo el HTML y la construcción del DOM está completa. (document.ready)

domComplete: Es el tiempo inmediatamente antes de que el agente de usuario establezca la preparación del documento actual en "complete", lo que significa que la página y todos sus recursos secundarios, como imágenes, han terminado de descargarse y están listos. (window.load)

firstContentfulPaint: Es el momento en que el navegador muestra el primer fragmento de contenido del DOM. Este es un hito importante para los usuarios porque proporciona comentarios de que la página realmente se está cargando.

Y básicamente es todo, pero aun hay más por explorar, si les interesa este tema, pueden seguir leyendo directamente desde el blog de Elasticsearch donde ahondan aun más sobre la integración, yo específicamente estoy interesado en una integración con Redux a través de un middleware, así es que seguramente escribiré un poco más adelante sobre esto.

Antes de que lo olvide, les dejo aqui la configuración del docker-compose que utilicé para poner en marcha elasticsearch, kibana y apm.


version: '3'

services:
    elasticsearch:
        image: docker.elastic.co/elasticsearch/elasticsearch:7.6.1
        container_name: elasticsearch
        restart: always
        volumes:
            - ./elastic:/usr/share/elasticsearch/data
        ports:
            - '9300:9300'
            - '9200:9200'
        environment:
            - bootstrap.memory_lock=true
            - 'ES_JAVA_OPTS=-Xms512m -Xmx512m'
            - discovery.type=single-node
            - discovery.seed_hosts=es02,es03
            - node.name=es01
        ulimits:
            memlock:
                soft: -1
                hard: -1
        networks:
            - local
    kibana:
        image: docker.elastic.co/kibana/kibana:7.6.1
        container_name: kibana
        restart: always
        ports:
            - 5601:5601
        environment:
            ELASTICSEARCH_URL: http://elasticsearch:9200
            ELASTICSEARCH_HOSTS: http://elasticsearch:9200
        networks:
            - local
    apm:
        image: docker.elastic.co/apm/apm-server:7.9.3
        container_name: apm
        restart: always
        depends_on:
            - elasticsearch
            - kibana
        cap_add: ["CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"]
        cap_drop: ["ALL"]
        ports:
        - 8200:8200
        networks:
        - local
        command: >
            apm-server -e
                -E apm-server.rum.enabled=true
                -E setup.kibana.host=kibana:5601
                -E setup.template.settings.index.number_of_replicas=0
                -E apm-server.kibana.enabled=true
                -E apm-server.kibana.host=kibana:5601
                -E output.elasticsearch.hosts=["elasticsearch:9200"]
        healthcheck:
            interval: 10s
            retries: 12
            test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:8200/

networks:
  local:
    driver: bridge

No olvides dejarme un comentario y decirme que te ha parecido este artículo, si te ha funcionado o si has tenido algún problema.

Happy coding!


Photo by Hermes Rivera on Unsplash

Jack Fiallos

Jack Fiallos

Te gustó este artículo?