Angular: Real User Monitoring com Grafana Stack

Danilo Lima
7 min readSep 6, 2024

--

Nesse post vamos abordar o tema de Observabilidade e RUM dentro do contexto de aplicações Frontend e entender como integrar essas ferramentas em aplicações Angular, vamos nessa!

Contextualizando

Observabilidade é a prática de coletar, analisar e entender dados gerados por um sistema para garantir que ele esteja funcionando corretamente e para identificar e solucionar problemas quando eles ocorrerem. Essa prática se baseia em três pilares, popularmente conhecidos com Pilares da Observabilidade, que são Métricas, Logs e Traces.

  • Métricas — São dados referentes à infraestrutura ou ao sistema, capturados em um período de tempo, que ajudam a compreender a saúde da aplicação e da infraestrutura. Como exemplos podemos citar taxa de erros de respostas, uso de memória, uso de CPU, etc.
  • Logs — São registros detalhados de operações que ocorrem dentro da aplicação, muito importantes para compreender o que aconteceu dentro da aplicação em um determinado período.
  • Traces — São informações referente ao percurso de uma requisição através dos diferentes componentes do sistema, que nos permite rastrear uma operação desde a sua origem até o seu destino.

No contexto de aplicações web client-side o conceito ainda está caminhando apesar de ser bastante difundido em aplicações server-side, a falta de maturidade na aplicação desse conceito em aplicações Front-end leva a uma série de desafios no que diz respeito a garantir a performance, a satisfação dos usuários e à solução rápida de problemas.

Além disso, a falta de visão sobre como o usuário interage com a aplicação, por onde navega, onde clica, qual navegador utiliza e como a aplicação está realmente se comportando pode dificultar a investigação e solução de possíveis problemas, bem como o entendimento de qual aspecto da aplicação precisa ser melhorado de forma assertiva baseado em dados de usuários reais.

Felizmente, nos últimos anos tem surgido algumas ferramentas com esse objetivo, e hoje vamos falar sobre uma delas, o Grafana Faro!

Grafana Faro

O Grafana Faro é um SDK JavaScritpt Open Source, anunciado em 2022 pela Grafana Labs, que pode ser integrado em aplicações web para coletar dados de monitoramento de usuários reais, tais como métricas de performance, logs, eventos, exceções e traces.

https://grafana.com/static/assets/img/diagrams/grafana-oss-faro-diagram.svg

Esses dados passam por um fluxo envolvendo ferramentas como o Grafana Loki, Grafana Tempo e os dashboards de visualização do Grafana. Além disso, o SDK pode enviar dados para diversas outras ferramentas de observabilidade.

Integrando ao Angular

Para tanto vamos dividir a tarefa em duas etapas:

  1. Primeiro precisamos configurar a infraestrutura que irá receber, processar e permitir que os dados sejam visualizados graficamente.
  2. Integrar o SDK na nossa aplicação Angular para coletar e enviar os dados.

Preparando a infraestrutura

Vamos utilizar o Docker para configurar os componentes necessários.

version: '3'
services:
# aqui configuramos o grafana como ferramenta de visualização dos dados
grafana:
image: grafana/grafana:latest
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
- GF_FEATURE_TOGGLES_ENABLE=accessControlOnCall
- GF_INSTALL_PLUGINS=https://storage.googleapis.com/integration-artifacts/grafana-lokiexplore-app/grafana-lokiexplore-app-latest.zip;grafana-lokiexplore-app
ports:
- 3000:3000/tcp

# configuramos o grafana loki
loki:
image: grafana/loki:main
command: '-config.file=/etc/loki/loki-config.yaml'
ports:
- 3100:3100
volumes:
- ./loki-config.yaml:/etc/loki/loki-config.yaml

# configuramos o grafana alloy, que será o ponto
# de entrada para onde o grafana faro irá enviar os dados
faro_collector:
image: grafana/alloy:latest
ports:
- 12345:12345
- 3333:3333
volumes:
- ./config.alloy:/etc/alloy/config.alloy
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
depends_on:
- tempo

# definimos uma etapa de inicialização do grafana tempo,
# criando um container temporário somente para configurar
# permissões no volume
init_tempo:
image: &tempo_image grafana/tempo:latest
user: root
entrypoint:
- "chown"
- "10001:10001"
- "/var/tempo"
volumes:
- ./tempo-data:/var/tempo
# e por fim configuramos o container do grafana tempo
tempo:
image: *tempo_image
ports:
- 3200:3200
- 4318:4318
volumes:
- ./tempo.yaml:/etc/tempo.yaml
- ./tempo-data:/var/tempo

command: [ "-config.file=/etc/tempo.yaml" ]
depends_on:
- init_tempo

Além disso, precisamos definir as configurações para o Grafana Loki, bem como para o Grafana Tempo. Porém, para não estender demais o artigo, vamos focar apenas na configuração do agente (arquivo config.alloy). As demais configurações estão disponíveis nesse repositório.

Configuração do agente

// configura o agente que irá receber os dados vindo do frontend
faro.receiver "integrations_app_agent_receiver" {
server {
cors_allowed_origins = ["*"]
listen_address = "0.0.0.0"
listen_port = 3333
max_allowed_payload_size = "10MiB"

rate_limiting {
rate = 100
}
}

output {
// repassa os logs para a etapa de processamento e rotulamento
logs = [loki.process.add_label.receiver]
// repassa os traces para a etapa de persistência no Grafana Tempo
traces = [otelcol.exporter.otlphttp.trace_write.input]
}
}

// essa etapa é responsável por definir os rótulos criados a partir
// dos campos nos logs, para criar filtros no dashboard do grafana
loki.process "add_label" {
stage.logfmt {
mapping = {
"level" = "",
"kind" = "",
"type"="",
"app_name"="",
"message"= "",
"browser_name"= "",
"browser_version" = "",
}
}

stage.labels {
values = {
"level" = "level",
"kind" = "kind",
"type" = "type",
"app_name" = "app_name",
"message" = "message",
"browser_name" = "browser_name",
"browser_version" = "",

}
}
// repassa para a etapa de persistência de logs no Grafana Loki
forward_to = [loki.write.logs_write_client.receiver]
}

// envia os logs para o endpoint do servidor do Grafana oki
loki.write "logs_write_client" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
// aqui, definimos o exportador usando uma configuração para o
// coletor do Open Telemetry, ele irá coletar os traces e enviá-los
// via HTTP para o Grafana Tempo
otelcol.exporter.otlphttp "trace_write" {

client {
endpoint = "http://tempo:4318"
tls {
insecure = true
insecure_skip_verify = true
}
}
}

A primeira etapa está finalizada, agora é só executar o comando abaixo e verificar se todos os containers estão de pé!

docker compose up

Integrando o SDK ao Angular

Vamos usar uma aplicação Angular de exemplo, disponível nesse repositório: https://github.com/danilolmc/angular-observability

O processo é bastante simples, precisamos somente inicializar o SDK antes que a aplicação Angular seja completamente carregada, por isso usamos o Injection Token APP_INITIALIZER.

import { APP_INITIALIZER, ApplicationConfig, ErrorHandler, NgZone } from '@angular/core';
import { TracingInstrumentation } from '@grafana/faro-web-tracing';

import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { getWebInstrumentations, initializeFaro, MetaSession, ViewInstrumentation, WebVitalsInstrumentation } from '@grafana/faro-web-sdk';
import { GlobalErrorHandler } from './shared/error/global.handler';


export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
provideApiEndpoint(),
{
provide: APP_INITIALIZER,
useFactory: (zone: NgZone) => {
return function () {
zone.runOutsideAngular(() => {
initializeFaro({
url: 'http://localhost:3333/collect',
app: {
name: 'Product App',
version: '1.0',
},
sessionTracking: {
enabled: true,
persistent: true,
maxSessionPersistenceTime: 1 * 60 * 2000,
onSessionChange: (oldSession: MetaSession | null, newSession: MetaSession) => {
console.log(`Session ${oldSession == null ? 'created' : 'changed'}`, { oldSession, newSession })
},
},
instrumentations: [
# desabilidando a captura de erros do console do usuário
...getWebInstrumentations({
captureConsole: false,
}),
# instrumentação para gerar métricas do Core Web Vitals
new WebVitalsInstrumentation(),
# instrumentação para gerar dados de tracing
new TracingInstrumentation(),
]
})
});
}
},
multi: true,
deps: [NgZone]
},
{
provide: ErrorHandler,
useClass: GlobalErrorHandler
}

]
};

Detalhe que, como o SDK não consegue capturar automaticamente os erros lançados pelo Angular no console do navegador, precisamos configurar um handler para que ele seja disparado automaticamente quando um erro ocorrer.

Por fim, é só executar a aplicação e ver a mágica acontecer! Ao olhar na aba de Rede do DevTools do browser, você vai perceber que o SDK enviou automaticamente os dados de telemetria da aplicação Angular.

Para visualizar esses dados no Grafana é simples:

E esse é o resultado, após interagir com a aplicação Angular por alguns minutos:

Com isso, conseguimos visualizar diversos dados de um usuário real da aplicação, como métricas de Web Vitals, qual navegador o usuário está usando, em qual versão, número de erros acontecendo na aplicação, tipos de erros, número de acessos, quantas vezes o usuário iniciou e encerrou a sessão, dados detalhados de requisições e muito mais.

E não para por aí: o limite da customização depende do que você quer monitorar na sua aplicação, o que significa que dashboards ainda mais completos podem ser criados.

Conclusão

O Grafana Faro é uma ferramenta muito interessante no contexto abordado neste post, principalmente pela facilidade de integração e pelas possibilidades de extensão. Ele oferece uma visão mais palpável de como nossa aplicação se comporta diante de usuários reais. No entanto, é evidente que existe uma complexidade na manutenção desse tipo de infraestrutura. Apesar de soluções como o Grafana Cloud facilitarem a configuração e manutenção da infraestrutura para receber e lidar com dados de telemetria vindos do SDK, é importante avaliar a real necessidade desse tipo de solução para suas aplicações antes de considerar uma solução com essa complexidade.

Bem, é isso! A ideia era introduzir o tema, que ainda tem muito espaço para ser discutido no contexto de aplicações client-side. Espero que tenha curtido!

Repositório do projeto: https://github.com/danilolmc/angular-observability

Mais sobre o Grafana Faro SDK: https://grafana.com/oss/faro/

Mais sobre o Grafana Cloud: https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/

Até logo, dev! 😉

--

--

Danilo Lima
Danilo Lima

Written by Danilo Lima

Software developer working mainly with Frontend and Angular, Open Source Contributor and I usually give my 20 cents about technology and career.

No responses yet