farol

A lightweight, pure-Dart Real User Monitoring (RUM) client that speaks the Grafana Faro collector wire format (logs, events, measurements, exceptions batched to a JSON HTTP endpoint). It works with any Faro-compatible receiver, including Grafana Cloud's Faro endpoint, Grafana Alloy's faro.receiver, or the OpenTelemetry Collector faroreceiver (contrib).

Implements the Grafana Faro wire protocol. Not affiliated with or endorsed by Grafana Labs.

Install

dependencies:
  farol: ^0.1.0

Or, to track this repo directly instead of pub.dev:

dependencies:
  farol:
    git:
      url: https://github.com/thegorangers/farol.git
      path: packages/farol

Usage

import 'package:farol/farol.dart';

void main() async {
  Faro.initialize(FaroConfig(
    collectorUrl: Uri.parse('https://faro-collector.example.com/collect'),
    app: const FaroApp(name: 'my-app', version: '1.0.0'),
    headersProvider: () async => {'x-api-key': await readApiKeyFromStorage()},
  ));

  Faro.instance.pushEvent('app_started');
  Faro.instance.pushMeasurement('startup', {'duration_ms': 420});
  Faro.instance.setUser(id: 'user-123');

  await Faro.shutdown(); // flushes pending signals before process exit
}

Endpoint & auth model

  • collectorUrl: any Faro-compatible collector endpoint.
  • headersProvider: an optional Future<Map<String, String>> Function() called before every batch POST. Use it to attach whatever auth your collector expects (API key, bearer token, signed header) — the client itself has no built-in auth scheme, and reads no credentials from the environment.
  • transport: swap the default HttpFaroTransport for a FakeTransport (tests) or your own FaroTransport implementation (e.g. to redirect to a local debug sink).
  • Delivery is best-effort and fire-and-forget: send() swallows all transport errors and never surfaces them to the app. A dropped batch does not affect app behavior; RUM data may be lost silently under network failure — this is intentional for telemetry.

No-PII rule

farol never inspects payload contents for personal data — that responsibility is the caller's. Concretely:

  • Don't put raw PII (names, emails, phone numbers, precise geolocation, free-text user input) into pushEvent/pushLog/pushMeasurement context/attributes, or into setUser's attributes.
  • Use setUser(id: ...) with an opaque, non-reversible identifier, not an email or username.
  • Use the beforeSend hook on FaroConfig to scrub or drop payloads centrally before they leave the device, if you need a last-resort guard.

Design notes

  • Sampling: sampleRate on FaroConfig (default 1.0) is applied per session, not per event.
  • Batching: signals are buffered and flushed on batchInterval, when maxBatchSize is hit, or explicitly via Faro.instance.flush() / Faro.shutdown().

Libraries

farol