Guidance on Integrating React Frontend Metrics and exceptions with Prometheus and Grafana UI

Hi,

I am currently running Grafana UI and Prometheus backend on Ubuntu 24.04.2 LTS. I need guidance on how to push all client-side errors and performance metrics from a ReactJS-based frontend application to the Prometheus backend, and visualize them on the Grafana UI.

Please advise. Thanks in advance.

Best regards,

Kaushal

Check Grafana Faro OSS | Web SDK for real user monitoring (RUM)

@jangaraj Thanks for sharing the link. I have configured the below flow

React.js App + Faro SDK → Grafana Agent (logs) → Loki → Grafana UI

# cat /etc/grafana-agent.yaml
---
server:
  log_level: info

metrics:
  wal_directory: /tmp/agent/wal
  global:
    scrape_interval: 15s
  configs:
    - name: agent-metrics
      scrape_configs:
        - job_name: grafana-agent
          static_configs:
            - targets: ['localhost:12345']

logs:
  configs:
    - name: default
      positions:
        filename: /tmp/positions.yaml
      clients:
        - url: http://localhost:3100/loki/api/v1/push

      scrape_configs:
        # System logs
        - job_name: system-logs
          static_configs:
            - targets: ['localhost']
              labels:
                job: syslog
                __path__: /var/log/*.log

        # Faro push endpoint — make sure this port matches your React app
        - job_name: faro-react-errors
          loki_push_api:
            server:
              http_listen_port: 12347
            labels:
              job: faro
              source: react-vite-app
#
/usr/local/bin/grafana-agent --config.file=/etc/grafana-agent.yaml
ts=2025-08-09T16:44:20.645171843Z caller=main.go:75 level=info boringcryptoenabled=false
ts=2025-08-09T16:44:20.645840761Z caller=server.go:190 level=info msg="server listening on addresses" http=127.0.0.1:12345 grpc=127.0.0.1:12346 http_tls_enabled=false grpc_tls_enabled=false
ts=2025-08-09T16:44:20.646844216Z caller=node.go:85 level=info agent=prometheus component=cluster msg="applying config"
ts=2025-08-09T16:44:20.647118125Z caller=remote.go:180 level=info agent=prometheus component=cluster msg="not watching the KV, none set"
ts=2025-08-09T16:44:20.654679754Z caller=promtail.go:133 level=info component=logs logs_config=default msg="Reloading configuration file" md5sum=7945325a75760b7681633a050424f553
ts=2025-08-09T16:44:20.656347709Z caller=pushtarget.go:73 level=info component=logs logs_config=default msg="starting push server" job=faro_react_errors
ts=2025-08-09T16:44:20.6657651Z caller=zapadapter.go:78 level=info component=traces msg="Traces Logger Initialized"
ts=2025-08-09T16:44:20.671078776Z caller=pushtarget.go:240 level=info component=logs logs_config=default msg="stopping push server" job=faro_react_errors
ts=2025-08-09T16:44:20.673147077Z caller=wal.go:211 level=info agent=prometheus instance=243a344db344241f404868d04272fc76 msg="replaying WAL, this may take a while" dir=/tmp/agent/wal/243a344db344241f404868d04272fc76/wal
ts=2025-08-09T16:44:20.673664376Z caller=wal.go:260 level=info agent=prometheus instance=243a344db344241f404868d04272fc76 msg="WAL segment loaded" segment=0 maxSegment=2
ts=2025-08-09T16:44:20.673931526Z caller=wal.go:260 level=info agent=prometheus instance=243a344db344241f404868d04272fc76 msg="WAL segment loaded" segment=1 maxSegment=2
ts=2025-08-09T16:44:20.674179609Z caller=wal.go:260 level=info agent=prometheus instance=243a344db344241f404868d04272fc76 msg="WAL segment loaded" segment=2 maxSegment=2
ts=2025-08-09T16:44:20.679523686Z caller=promtail.go:133 level=info component=logs logs_config=default msg="Reloading configuration file" md5sum=b25b0554daf9a3d06c5046d56c3e0020
ts=2025-08-09T16:44:20.680745793Z caller=pushtarget.go:73 level=info component=logs logs_config=default msg="starting push server" job=faro_react_errors
panic: duplicate metrics collector registration attempted

goroutine 1 [running]:
github.com/prometheus/client_golang/prometheus.(*Registry).MustRegister(0xc00186ed70, {0xc000080d00?, 0x0?, 0x0?})
	/go/pkg/mod/github.com/prometheus/client_golang@v1.19.0/prometheus/registry.go:405 +0x66
github.com/prometheus/client_golang/prometheus/promauto.Factory.NewGaugeVec({{0x8ea9e80?, 0xc00186ed70?}}, {{0xc000cc3de0, 0x1a}, {0x0, 0x0}, {0x7bd9a51, 0xf}, {0x7cfb98e, 0x2b}, ...}, ...)
	/go/pkg/mod/github.com/prometheus/client_golang@v1.19.0/prometheus/promauto/auto.go:308 +0x163
github.com/grafana/dskit/server.NewServerMetrics({{0xc000cc3de0, 0x1a}, 0x0, {0x7ba3412, 0x3}, {0x0, 0x0}, 0x303b, 0x0, {0x7ba3412, ...}, ...})
	/go/pkg/mod/github.com/grafana/dskit@v0.0.0-20240104111617-ea101a3b86eb/server/metrics.go:30 +0x136
github.com/grafana/dskit/server.New({{0xc000cc3de0, 0x1a}, 0x0, {0x7ba3412, 0x3}, {0x0, 0x0}, 0x303b, 0x0, {0x7ba3412, ...}, ...})
	/go/pkg/mod/github.com/grafana/dskit@v0.0.0-20240104111617-ea101a3b86eb/server/server.go:240 +0x3c
github.com/grafana/loki/clients/pkg/promtail/targets/lokipush.(*PushTarget).run(0xc001a81ec0)
	/go/pkg/mod/github.com/grafana/loki@v1.6.2-0.20240510183741-cef4c2826b4b/clients/pkg/promtail/targets/lokipush/pushtarget.go:91 +0x418
github.com/grafana/loki/clients/pkg/promtail/targets/lokipush.NewPushTarget({0x8e565c0, 0xc001071540}, {0x8e7c000, 0xc000b2afe0}, {0x0, 0x0, 0x0}, {0xc001aa8ca8, 0x11}, 0xc0011e9888)
	/go/pkg/mod/github.com/grafana/loki@v1.6.2-0.20240510183741-cef4c2826b4b/clients/pkg/promtail/targets/lokipush/pushtarget.go:64 +0x26b
github.com/grafana/loki/clients/pkg/promtail/targets/lokipush.NewPushTargetManager({0x8ea9b90, 0xc0010d4798}, {0x8e565c0, 0xc001071540}, {0x8e7bfd8, 0xc001a829b0}, {0xc0011f6f08, 0x1, 0x1})
	/go/pkg/mod/github.com/grafana/loki@v1.6.2-0.20240510183741-cef4c2826b4b/clients/pkg/promtail/targets/lokipush/pushtargetmanager.go:47 +0x2a5
github.com/grafana/loki/clients/pkg/promtail/targets.NewTargetManagers({0x8e578a0?, 0xc000f6c000?}, {0x8ea9b90, 0xc0010d4798}, {0x8e565c0, 0xc001071540}, {0x2540be400, {0xc00104e6a8, 0x13}, 0x0, ...}, ...)
	/go/pkg/mod/github.com/grafana/loki@v1.6.2-0.20240510183741-cef4c2826b4b/clients/pkg/promtail/targets/manager.go:222 +0x1cc5
github.com/grafana/loki/clients/pkg/promtail.(*Promtail).reloadConfig(0xc000f6c000, 0xc000fe6c08)
	/go/pkg/mod/github.com/grafana/loki@v1.6.2-0.20240510183741-cef4c2826b4b/clients/pkg/promtail/promtail.go:187 +0xc39
github.com/grafana/loki/clients/pkg/promtail.New({{{0x0, 0x0}}, {{{0x0, 0x0}, 0x0, {0x0, 0x0}, {0x0, 0x0}, 0x0, ...}, ...}, ...}, ...)
	/go/pkg/mod/github.com/grafana/loki@v1.6.2-0.20240510183741-cef4c2826b4b/clients/pkg/promtail/promtail.go:110 +0x2bd
github.com/grafana/agent/static/logs.(*Instance).ApplyConfig(0xc001515e40, 0xc00106d220, {{0x0, 0x0}, {0x0, 0x0, 0x0}}, 0x0)
	/drone/src/static/logs/logs.go:219 +0xb07
github.com/grafana/agent/static/logs.(*Logs).ApplyConfig(0xc001667980, 0x2540be400?, 0x0)
	/drone/src/static/logs/logs.go:70 +0x205
main.(*Entrypoint).ApplyConfig(_, {0xc000cd0140, {{{0x37e11d600, 0x2540be400, 0xdf8475800, {...}, {...}, 0x0, 0x0, 0x0, ...}, ...}, ...}, ...})
	/drone/src/cmd/grafana-agent/entrypoint.go:168 +0x60e
main.NewEntrypoint(0xc00104ca20, 0xc0011f3c08, 0x82520c0)
	/drone/src/cmd/grafana-agent/entrypoint.go:112 +0x5a5
main.main()
	/drone/src/cmd/grafana-agent/main.go:76 +0x368

Please guide me the next steps.

Best regards,

Kaushal

Caution

Grafana Alloy is the new name for our distribution of the OTel collector. Grafana Agent has been deprecated and is in Long-Term Support (LTS) through October 31, 2025. Grafana Agent will reach an End-of-Life (EOL) on November 1, 2025. Read more about why we recommend migrating to Grafana Alloy.

@jangaraj Hi,

I have created the /etc/alloy/config.alloy configuration file to enable the data flow from React via Faro SDK to Alloy OTLP Receiver, then to Loki Exporter, Loki, and finally Grafana.

#cat /etc/alloy/config.alloy
server main_server {
  listen_address = "0.0.0.0"
  listen_port = 4317
}

cors main_cors {
  allowed_origins = ["*"]
}

receivers otlp_receiver {
  otlp {
    protocols {
      grpc = true
      http = true
    }
  }
}

exporters loki_exporter {
  loki {
    endpoint = "http://localhost:3100/loki/api/v1/push"
    labels = ["app", "environment"]
    tenant_id = ""
  }
}

processors batch_processor {
  batch {}
}

processors attributes_processor {
  attributes {
    actions = [
      { action = "insert", key = "app", value = "react-faro" },
      { action = "insert", key = "environment", value = "production" },
    ]
  }
}

service main_service {
  pipelines {
    logs {
      receivers = ["otlp_receiver"]
      processors = ["batch_processor", "attributes_processor"]
      exporters = ["loki_exporter"]
    }
  }
}
#alloy validate /etc/alloy/config.alloy
Error: /etc/alloy/config.alloy:1:8: expected block label, got IDENT

1 | server main_server {
  |        ^
2 |   listen_address = "0.0.0.0"

Error: /etc/alloy/config.alloy:6:6: expected block label, got IDENT

5 |
6 | cors main_cors {
  |      ^
7 |   allowed_origins = ["*"]

Error: /etc/alloy/config.alloy:10:11: expected block label, got IDENT

 9 |
10 | receivers otlp_receiver {
   |           ^
11 |   otlp {

Error: /etc/alloy/config.alloy:19:11: expected block label, got IDENT

18 |
19 | exporters loki_exporter {
   |           ^
20 |   loki {

Error: /etc/alloy/config.alloy:27:12: expected block label, got IDENT

26 |
27 | processors batch_processor {
   |            ^
28 |   batch {}

Error: /etc/alloy/config.alloy:31:12: expected block label, got IDENT

30 |
31 | processors attributes_processor {
   |            ^
32 |   attributes {

Error: /etc/alloy/config.alloy:40:9: expected block label, got IDENT

39 |
40 | service main_service {
   |         ^
41 |   pipelines {

Error: validation failed

Please guide me. Thanks in advance.

Best regards,

Kaushal

If you use the alloy convert function, you can easy get a working alloy config based on your agent config.

prometheus.scrape "metrics_agent_metrics_grafana_agent" {
        targets = [{
                __address__ = "localhost:12345",
        }]
        forward_to      = [prometheus.remote_write.metrics_agent_metrics.receiver]
        job_name        = "grafana-agent"
        scrape_interval = "15s"
}

local.file_match "logs_default_system_logs" {
        path_targets = [{
                __address__ = "localhost",
                __path__    = "/var/log/*.log",
                job         = "syslog",
        }]
}

loki.source.file "logs_default_system_logs" {
        targets               = local.file_match.logs_default_system_logs.targets
        forward_to            = [loki.write.logs_default.receiver]
        legacy_positions_file = "/tmp/positions.yaml"
}

loki.source.api "logs_default_faro_react_errors" {
        http {
                listen_port = 12347
        }

        grpc { }
        graceful_shutdown_timeout = "0s"
        forward_to                = [loki.write.logs_default.receiver]
        labels                    = {
                job    = "faro",
                source = "react-vite-app",
        }
        relabel_rules = null
}

loki.write "logs_default" {
        endpoint {
                url = "http://localhost:3100/loki/api/v1/push"
        }
        external_labels = {}
}