Ingeniería

Una muestra de RUM (monitoreo de usuario real) de Elastic

Lo sentimos si te tentamos con un maravilloso cóctel hecho con ron, y luego te diste cuenta de que el RUM del que estamos hablando no es el ron que anhelas. Pero quédate tranquilo que el RUM de Elastic es igualmente maravilloso. Echemos un vistazo. Queremos advertirte que tomará un poco de tiempo revisar la cantidad de detalles que cubriremos en este blog.

¿Qué es RUM?

El monitoreo de usuario real (RUM) de Elastic captura las interacciones del usuario con el navegador web y proporciona una vista detallada de la "experiencia del usuario real" de tus aplicaciones web desde una perspectiva de rendimiento. El agente RUM de Elastic es un agente de JavaScript, lo que significa que es compatible con cualquier aplicación basada en JavaScript. RUM puede proporcionar información valiosa sobre tus aplicaciones. Algunos de los beneficios comunes de RUM incluyen lo siguiente:

  • Los datos de rendimiento de RUM pueden ayudarte a identificar cuellos de botella y descubrir cómo los problemas de rendimiento del sitio afectan la experiencia de tus visitantes.
  • La información de agente de usuario capturada por RUM te permite identificar los navegadores, los dispositivos y las plataformas más usados por tus clientes para que puedas realizar optimizaciones informadas a tu aplicación.
  • Junto con la información de ubicación, los datos de rendimiento del usuario individual de RUM te ayudan a comprender el rendimiento regional de tu sitio web en todo el mundo.
  • RUM proporciona información y medición para los acuerdos de nivel de servicio (SLA) de tus aplicaciones.
  • RUM reúne información sobre el comportamiento de clics y visita de los clientes en el tiempo que puede ser útil para que los equipos de desarrollo identifiquen el impacto de características nuevas.

Primeros pasos con RUM usando APM de Elastic

En este blog, te guiaremos por el proceso completo de instrumentación de una aplicación web simple realizada a partir de un frontend React y un backend Spring Boot, paso a paso. Verás lo fácil que es usar el agente RUM. Como beneficio adicional, también podrás ver cómo el APM de Elastic vincula la información de rendimiento de backend y frontend junto con una vista de rastreo distribuido holístico. Consulta el blog anterior para obtener una visión general de APM de Elastic y el rastreo distribuido si estás interesado en conocer más detalles.

Para usar el monitoreo de usuario real de APM de Elastic, tienes que tener el Elastic Stack con el servidor APM instalado. Puedes, por supuesto, descargar e instalar la última versión del Elastic Stack con el servidor de APM de forma local en tu computadora. Sin embargo, el enfoque más sencillo sería crear una cuenta de prueba de Elastic Cloud y tener tu cluster listo en unos minutos. APM está habilitado para la plantilla predeterminada de E/S optimizada. De ahora en adelante, supondremos que tienes un cluster listo para usar.

Aplicación de muestra

La aplicación que instrumentaremos es una aplicación simple de base de datos de automóviles realizada a partir de un frontend React y un backend Spring Boot que proporciona acceso de API a una base de datos de automóviles en la memoria. La aplicación se mantiene simple a propósito. La idea es mostrarte pasos detallados desde cero para que puedas instrumentar tus propias aplicaciones siguiendo los mismos pasos.

A simple application with a React frontend and Spring backend

Crea un directorio llamado CarApp en cualquier parte de tu computadora portátil. Luego, clona tanto la aplicación de frontend como la aplicación de backend en ese directorio.

git clone https://github.com/carlyrichmond/carfront
git clone https://github.com/carlyrichmond/cardatabase

Como puedes ver, la aplicación es extremadamente simple. Solo hay un par de componentes en el frontend React y algunas clases en la aplicación de backend Spring Boot. Crea y ejecuta la aplicación siguiendo las instrucciones en GitHub para el frontend y el backend. Deberías ver algo así. Puedes navegar, filtrar automóviles y ejecutar opciones de crear, leer, actualizar y eliminar (CRUD) en ellos.

The simple React user interface

Ahora, con la aplicación en ejecución, estamos listos para pasar por la instrumentación usando el agente RUM.

Una completa instrumentación lista para usar con RUM

Se necesita un servidor de APM de Elastic para comenzar. Tendrás que habilitar RUM para capturar los eventos de tu agente RUM. Existen dos formas de configurar el agente RUM:

  1. Puedes instalar el agente RUM como una dependencia del proyecto a través de un gestor de paquetes como npm:
    npm install @elastic/apm-rum --save
  2. Incluye el agente RUM a través de la etiqueta script HTML. Ten en cuenta que esto puede realizarse tanto como una operación con bloqueo o como sin bloqueo según la documentación.
    <script 
    src="https://unpkg.com/@elastic/apm-rum@5.12.0/dist/bundles/elastic-apm-rum.umd.min.js">
    </script>
    <script>
    elasticApm.init({
    serviceName: 'carfront',
    serverUrl: 'http://localhost:8200',
    serviceVersion: '0.90'
    })
    </script>

Dado que nuestro frontend es una aplicación React, vamos a usar el primer enfoque. Una vez que hayas instalado @elastic/apm-rum en tu proyecto, echa un vistazo al código de inicialización en rum.js. Se encuentra en el mismo directorio que index.js y se verá parecido a esto, pero en vez de serviceUrl aparecerá tu propio endpoint del servidor APM:

import { init as initApm } from '@elastic/apm-rum'
var apm = initApm({
//Configurar nombre de servicio obligatorio (caracteres válidos: a-z, A-Z, 0-9, -, _ y espacio)
serviceName: 'carfront',
// Configurar la versión de tu aplicación
// Se usa en el servidor APM para encontrar el mapa fuente correcto
serviceVersion: '0.90',
// Configura la URL del servidor APM (predeterminada: http://localhost:8200)
serverUrl: 'APM_URL',
// distributedTracingOrigins: ['http://localhost:8080'],
})
export default apm;

Eso es todo lo que se necesita para inicializar el agente RUM. Si usas características específicas del marco de trabajo, como el enrutamiento en React, Angular o Vue, también te recomendamos instalar y configurar las integraciones específicas del marco de trabajo, que se explican en la documentación. En este caso, como es una sola página que no requiere la instrumentación específica de React, no instalamos la dependencia adicional.

No te preocupes por distributedTracingOrigins en este momento. Esta es una explicación rápida de algunas de las otras configuraciones:

  1. Nombre del servicio: Se debe establecer el nombre del servicio. Representa tu aplicación en la UI de APM. Asígnale un nombre significativo.
  2. Versión del servicio: Esta es la versión de tu aplicación. El servidor de APM también usa esta versión para encontrar el mapa de origen correcto. Hablaremos del mapa de origen en detalle más adelante.
  3. URL del servidor: esta es la URL del servidor APM. Ten en cuenta que normalmente se puede acceder al servidor APM desde la internet pública, ya que tu agente RUM informa datos al servidor desde navegadores de usuario final en internet.

Las personas que están familiarizadas con los agentes de backend de APM de Elastic podrían preguntarse por qué el token de APM no se pasó aquí. Esto se debe a que el agente RUM en realidad no usa un token de APM secreto. El token solo se usa para agentes de backend. Como el código de frontend es público, el token secreto no proporciona seguridad adicional.

Cargaremos este archivo de JavaScript cuando se cargue la aplicación y lo incluiremos en los lugares donde queramos realizar una instrumentación personalizada. Por ahora, veamos qué cosas tenemos listas para usar, sin ninguna instrumentación personalizada. Para hacerlo, simplemente debemos incluir rum.js en index.js. El archivo index.js importa rum.js y establece un nombre de carga de página. Si no se establece un nombre de carga de página, verás la carga de la página como "/" en la UI de APM, que no es muy intuitiva. Así es como se ve index.js.

import apm from './rum'
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
apm.setInitialPageLoadName("Car List")
ReactDOM.render(, document.getElementById('root'));
serviceWorker.unregister();

Accede a las páginas y agrega o elimina automóviles para generar un poco de tráfico a tu aplicación. Luego inicia sesión en Kibana y haz clic en el mosaico Observability. Desde allí, selecciona la opción Services (Servicios) en el submenú de APM, como se muestra a continuación:

Deberías ver un servicio llamado "carfront" en la lista. Al hacer clic en el nombre del servicio, se accede a la página de transacciones. Deberías ver una visión general de las métricas, como la latencia y el rendimiento del período predeterminado "Last 15 minutes" (Últimos 15 minutos). De lo contrario, cambia el selector de tiempo a dicho rango.

En el segmento de transacciones, deberías ver la transacción "Car List". Haz clic en el enlace "Car List" y pasarás a la pestaña Transaction (Transacción), que contiene las estadísticas de esta muestra de transacciones. Si te desplazas a la parte inferior de la página, verás una vista en cascada de interacciones de navegador como esta:

¿Asombrado por la cantidad de información capturada por el agente RUM de forma predeterminada? Presta especial atención a los marcadores en la parte superior, como timeToFirstByte, domInteractive, domComplete y firstContentfulPaint. Pasa el mouse sobre los puntos negros para ver los nombres. Te proporcionan una gran cantidad de detalles sobre la recuperación de contenido y la representación en el navegador de estos contenidos. También presta atención a todos los datos de rendimiento sobre la carga de recursos desde el navegador. Con solo inicializar tu agente RUM, sin instrumentación personalizada, obtienes todas estas métricas detalladas de rendimiento, listas para usar. Cuando hay un problema de rendimiento, estas métricas te permiten decidir fácilmente si el problema se debe a servicios de backend lentos, una red lenta o simplemente un navegador de cliente lento. Eso es muy impresionante.

Para aquellos de ustedes que necesitan un repaso, aquí incluimos una explicación rápida de las métricas de rendimiento web. Ten en cuenta que para los marcos de trabajo de aplicaciones web modernas como React, estas métricas podrían representar solo la parte “estática” de la página web, debido a la naturaleza asíncrona de React. Por ejemplo, es posible que los contenidos dinámicos sigan cargándose después de domInteractive, como podrás ver más adelante.

  • timeToFirstByte es la cantidad de tiempo que un navegador espera para recibir el primer elemento de información del servidor web después de solicitarlo. Representa una combinación la velocidad de red y de procesamiento del lado del servidor.
  • domInteractive es el momento inmediatamente antes de que el agente de usuario establezca la preparación del documento actual en "interactive" (interactivo), lo que significa que el navegador ha terminado de parsear todo el HTML y la creación de DOM se completó.
  • domComplete es el momento inmediatamente antes de que el agente de usuario establezca la preparación del documento actual en "complete" (completo), lo que significa que la página y todos sus recursos secundarios, como las imágenes, se han terminado de descargar y están listos. La rueda de carga ha dejado de girar.
  • firstContentfulPaint es el momento en que el navegador muestra el primer extracto de contenido del DOM. Este es un hito importante para los usuarios porque proporciona comentarios de que la página realmente se está cargando.

Instrumentación personalizada flexible

El agente RUM proporciona instrumentación detallada para la interacción de tu navegador, lista para usarse, como acabas de ver. También puedes realizar instrumentaciones personalizadas cuando sea necesario. Por ejemplo, debido a que la aplicación React es una aplicación de una sola página y eliminar un automóvil no desencadenará una “carga de página”, RUM no captura de forma predeterminada los datos de rendimiento de la eliminación de un automóvil. Podemos usar transacciones personalizadas para algo así.

Con nuestra versión actual (APM Real User Monitoring JavaScript Agent 5.x), las llamadas de AJAX y los eventos de clic son capturados por el agente y enviados al servidor APM. Se puede lograr la configuración de los tipos de interacciones con el ajuste disableInstrumentation.

También es posible agregar tus propias instrumentaciones de personalización para dar rastreos más significativos. Esto puede resultar particularmente útil para rastrear características nuevas. En nuestra aplicación de ejemplo, el botón "New Car" (Nuevo automóvil) en nuestra aplicación de frontend te permite agregar un nuevo automóvil a la base de datos. Instrumentaremos el código para capturar el rendimiento de agregar un automóvil nuevo. Abre el archivo Carlist.js en el directorio de componentes. Verás el siguiente código:

//Agregar automóvil nuevo
addCar(car) {
// Agregar metadatos de automóvil como etiquetas a la transacción de clics de RUM
var transaction = apm.startTransaction("Add Car", "Car");
transaction.addLabels(car);
fetch(SERVER_URL + 'api/cars',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(car)
})
.then(res => this.fetchCars())
.catch(err => console.error(err))
}
fetchCars = () => {
fetch(SERVER_URL + 'api/cars')
.then((response) => response.json())
.then((responseData) => {
this.setState({
cars: responseData._embedded.cars,
});
})
.catch(err => console.error(err));
// Terminar la transacción actual al final de la llamada de respuesta
var transaction = apm.getCurrentTransaction()
if (transaction) transaction.end()
}

El código creó básicamente una nueva transacción llamada "Add Car" (Agregar automóvil) de tipo "Car" (Automóvil). Luego, etiquetó la transacción con el automóvil para proporcionar información contextual. Luego terminamos explícitamente la transacción al final del método.

Agrega un automóvil nuevo desde la UI web de la aplicación. Haz clic en la UI de APM en Kibana. Deberías ver una transacción denominada “Add Car” (Agregar automóvil) enumerada. Asegúrate de seleccionar “Car” (Automóvil) en el menú desplegable de “Filter by type” (Filtrar por tipo). De forma predeterminada, muestra las transacciones de "page-load".

Haz clic en el enlace de transacción "Add Car" (Agregar automóvil). Deberías ver la información de rendimiento de la transacción personalizada "Add Car" (Agregar automóvil):

Haz clic en la pestaña "Metadata" (Metadatos). Verás las etiquetas que agregamos junto con las etiquetas predeterminadas que capturó el agente. Las etiquetas y los logs agregan información contextual valiosa a tus rastreos de APM.

Realmente eso es todo lo que hace falta para hacer una instrumentación personalizada; fácil pero potente. Para obtener más detalles, consulta la documentación de API.

Dashboard de experiencia del usuario

El APM de Elastic ofrece una UI de APM curada y dashboards de APM integrados para visualizar todos los datos de APM capturados por los agentes de forma inmediata.

También puedes crear tus propias visualizaciones personalizadas en Elastic con pipelines del nodo de ingesta para enriquecer y transformar tus datos de APM. Por ejemplo, los datos de IP de usuario y de agente de usuario capturados por el agente RUM representan información muy detallada sobre tus clientes. Con toda la información de la IP de usuario y el agente de usuario, es posible crear una visualización como esta para mostrar de dónde proviene el tráfico web en un mapa y qué sistemas operativos y navegadores están utilizando tus clientes.

Sin embargo, muchos de los datos de interés de los usuarios podrían estar presentes en el dashboard User Experience (Experiencia del usuario) visible en Elastic Observability. A continuación puedes ver algunas visualizaciones de muestra:

Obtén una visión general con el rastreo distribuido

Como punto de bonificación, también instrumentaremos nuestra aplicación de backend Spring Boot para que tengas una vista completa de la transacción general desde el navegador web hasta la base de datos de backend, todo en una sola vista. El rastreo distribuido de APM de Elastic te permite hacer esto.

Configurar el rastreo distribuido en agentes RUM

El rastreo distribuido está habilitado de manera predeterminada en el agente RUM. Sin embargo, solo incluye solicitudes realizadas al mismo origen. Para incluir solicitudes de origen cruzado, debes establecer la opción de configuración distributedTracingOrigins. También tendrás que configurar la política CORS en la aplicación de backend, como veremos en la siguiente sección.

Para nuestra aplicación, el frontend se toma desde http://localhost:3000. Para incluir las solicitudes realizadas a http://localhost:8080, debemos agregar la configuración distributedTracingOrigins a nuestra aplicación React. Esto se hace dentro de rum.js. El código ya está ahí. Simplemente descomentando la línea se logrará esto.

var apm = initApm({
...
distributedTracingOrigins: ['http://localhost:8080']
})

Las versiones nuevas de agentes implementan la especificación W3C Trace Context y el encabezado traceparent en las solicitudes realizadas a http://localhost:8080. Sin embargo, ten en cuenta que antes esto se lograba agregando el encabezado personalizado elastic-apm-traceparent a estas solicitudes.

Según la versión más reciente de la documentación, la instrumentación del lado del servidor puede configurarse de tres formas posibles:

  1. Agregado automático a la JVM en ejecución con apm-agent-attach-cli.jar
  2. Configuración programática con apm-agent-attach, que requiere un cambio de código en tu aplicación Java
  3. Configuración manual con la etiqueta -javaagent, que es lo que haremos en el ejemplo a continuación

Para usar el enfoque de instrumentación manual en el lado del servidor, debes descargar el agente Java y ejecutar tu aplicación con este agente. En tu IDE favorito, tendrás que agregar los vmArgs siguientes a la configuración de inicio.

-javaagent:apm/wrapper/elastic-apm-agent-1.33.0.jar 
-Delastic.apm.service_name=cardatabase
-Delastic.apm.application_packages=com.packt.cardatabase
-Delastic.apm.server_urls=
-Delastic.apm.secret_token=

Si usas Elastic Cloud, puedes encontrar la configuración completa para el RUM y los agentes de APM en la integración de APM de tu despliegue, de lo cual puedes ver una muestra a continuación.

Dónde se configuran los agentes dependerá del IDE que prefieras. La siguiente es una captura de pantalla de la configuración de inicio de mi VSCode para la aplicación Spring Boot:

Ahora, actualiza tu lista de automóviles desde el navegador para generar otra solicitud. Dirígete a la UI de APM de Kibana y consulta la última carga de la página "car list" (Lista de automóviles). Deberías ver un rastreo completo que incluya las invocaciones del método Java, similar a la captura de pantalla siguiente:

Como puedes ver, tus datos de rendimiento del lado del cliente desde el navegador y tus datos de rendimiento del lado del servidor, incluido el acceso de JDBC, se muestran perfectamente en un solo rastreo distribuido. Observa los diferentes colores para las diferentes partes del rastreo distribuido. Ten en cuenta que este es el rastreo predeterminado que obtienes, sin tener que hacer ninguna instrumentación personalizada en el lado del servidor, aparte de iniciar tu aplicación con el agente. Siente el poder de APM de Elastic y el rastreo distribuido.

Los lectores que realmente están prestando atención a la línea de tiempo de visualizaciones anterior posiblemente se pregunten por qué la transacción page-load de "Car List" (Lista de automóviles) finaliza en 193 ms, que es el tiempo de domInteractive, mientras que los datos aún se están tomando desde el backend. Gran pregunta. Esto se debe al hecho de que las llamadas de obtención son asíncronas de forma predeterminada. El navegador “cree” que terminó de parsear todo el HTML y que la construcción de DOM se completó a los 193 ms porque cargó todos los contenidos HTML “estáticos” tomados desde el servidor web. Por otro lado, React todavía está cargando datos del servidor de backend de forma asíncrona.


Intercambio de recursos de origen cruzado (CORS)

El agente RUM es solo una pieza del rompecabezas en un rastreo distribuido. Para usar el rastreo distribuido, también necesitamos configurar correctamente otros componentes. Una de las cosas que normalmente tendrás que configurar es el intercambio de recursos de origen cruzado, el "notorio" CORS. Esto se debe a que los servicios de frontend y backend generalmente se despliegan por separado. Con la política same-origin, tus solicitudes de frontend desde un origen diferente al backend fallarán sin un CORS configurado correctamente. Básicamente, CORS es una forma que le permite al lado del servidor revisar si las solicitudes que entran desde un origen diferente están permitidas. Para leer más sobre las solicitudes de origen cruzado y por qué este proceso es necesario, consulta la página de MDN sobre Intercambio de recursos de origen cruzado.

¿Qué significa eso para nosotros? Significa dos cosas:

  1. Debemos establecer la opción de configuración distributedTracingOrigins como lo hemos hecho.
  2. Con esa configuración, el agente RUM también envía una solicitud HTTP OPTIONS (Opciones de HTTP) antes de la solicitud de HTTP real para asegurarse de que todos los encabezados y métodos de HTTP sean compatibles y se permita el origen. Específicamente, http://localhost:8080 recibirá una solicitud OPTIONS (Opciones) con los siguientes encabezados:
    Access-Control-Request-Headers: traceparent, tracestate
    Access-Control-Request-Method: [request-method]
    Origin: [request-origin]
    Y el servidor APM debería responder con estos encabezados y un código de estado 200:
    Access-Control-Allow-Headers: traceparent, tracestate
    Access-Control-Allow-Methods: [allowed-methods]
    Access-Control-Allow-Origin: [request-origin]

La clase MyCorsConfiguration en nuestra aplicación Spring Boot hace exactamente eso. Hay diferentes formas de configurar Spring Boot para que haga esto, pero aquí estamos usando un enfoque basado en filtros. Se trata de configurar nuestra aplicación Spring Boot del lado del servidor para permitir las solicitudes desde cualquier origen con cualquier encabezado de HTTP y cualquier método de HTTP. Posiblemente no desees ser así de abierto con tus aplicaciones de producción.

@Configuration
public class MyCorsConfiguration {
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}

Resumen

Esperamos que este blog haya dejado en claro que instrumentar tus aplicaciones con RUM de Elastic es un proceso simple y fácil, pero extremadamente poderoso. Junto con otros agentes de APM para servicios de backend, RUM te ofrece una visión holística del rendimiento de la aplicación desde una perspectiva de usuario final a través del rastreo distribuido.

Una vez más, para comenzar con APM de Elastic, puedes descargar el servidor APM de Elastic para ejecutarlo localmente o crear una cuenta de prueba de Elastic Cloud y tener un cluster listo en unos minutos.

Como siempre, accede al foro de Elastic APM si deseas abrir un debate o tienes alguna pregunta. ¡Feliz comienzo de RUM!

Este blog se publicó originalmente el 1 de abril de 2019. Se actualizó el 20 de octubre de 2022.