Frontend Observability using Grafana Faro

Asim Iqbal Siddiqui
Bazaar Engineering
Published in
6 min readNov 30, 2023

Grafana Faro is a data visualization and analytics platform designed for observability, providing insights into system through metrics, logs, and traces for frontend.

Grafana Faro Architecture

Note:

In this article we will cover the on perm cloud setup through flow mode of Grafana Faro by creating a river file. You can follow this doc if you want to use plug and play Grafana Faro observability setup.

Grafana Faro is in public review as of (Dec, 2023), you can track the details here.

Before jumping to this article, I highly recommend you to deep dive into what actually frontend observability and particularly tracing and web vitals do. Here is a 5 mint read for this.

Loki for Logs

In Grafana, Loki operates as a log aggregation system, serving as the underlying infra for the storage and retrieval of log data. Loki seamlessly integrates with Grafana to facilitate the systematic collection and querying of log information.

Tempo for Traces

Tempo enables the efficient collection, storage and retrieval of tracing data. Grafana utilises OpenTelemetry as its instrumentation framework for adding traces support to applications. It enables developers to capture traces seamlessly. The integration of OpenTelemetry with Grafana enhances the platform’s capability to provide end-to-end observability. It offers a standardised approach to instrumenting applications.

The collected data, including traces from Tempo and logs from Loki, from various sources, can be visualised and analysed in Grafana.

But wait, who actually does the job of collecting data for Loki and Tempo? This is where Grafana Agent joins the chat.

Grafana Agent

Grafana Agent is a vendor-neutral telemetry collector with configuration. It is designed to be flexible, performant, and compatible with multiple ecosystems such as Prometheus and OpenTelemetry.

The flow mode of Grafana faro is particularly inspired by Terraform. It operates around components. Each components, after performing set of operations, can forward the data to other component to wire-up observability pipelines for OpenTelemetry data collection, processing and delivery. Flow mode is helpful if you want to have Reusability, Component Chaining, and have components that performs single task only.

These components are written using .river file. Here is the minimal example of river file configuration for our use case of handling traces using Tempo and logs using Loki.

// faro.river file

faro.receiver "faroReceiver" {
server {
cors_allowed_origins = ["*"]
listen_address = "0.0.0.0"
api_key="<RANDOM_GENERATED_API_KEY_HERE>"
}
output {
logs = [loki.process.local.receiver]
traces = [otelcol.exporter.otlp.traces.input]
}
}
loki.write "default" {
endpoint {
url = "https://<LOKI_HOST>.COM/path/to/collect/logs"
tenant_id = "<TENANT_ID>"
}
}

otelcol.exporter.otlp "traces" {
client {
endpoint = "TEMP_HOST:<PORT_IF_ANY>"
tls {
insecure = true
insecure_skip_verify = true
}
}
}

loki.process "local" {
forward_to = [loki.write.default.receiver]
stage.logfmt {
mapping = { "app_name"="","session_id"="" }
}
stage.labels {
values = {
"app_name"= "app_name",
"session_id" = "session_id",
}
}
}

You can see each component does exactly one job and then forward the result to other component wiring up the pipeline. The entry point of these components is faro.receiver. To know about more components, refer here.

Faro Instrumentations

Faro provides instrumentations which are responsible to collect data on your frontend application. The good news is they are very easy to integrate, and does most of the job out of the box.

  • Console Instrumentation: This intercepts the browser consoling and logs the data to grafana agent automatically. For example, if a console.warn is triggered, console instrumentation will automatically sends the data to Grafana Agent. Console Instrumentation disables few logs by default, i.e.
  • console.debug
  • console.trace
  • console.log
  • Error Instrumentation: If your app throws any unwanted error, this instrumentation will auto collect the error and send the data to Grafana Agent. Its default kind is “log”. However, if the error is caught in try/catch block, the error should be thrown manually using faro api.
  • Web Vitals Instrumentation: Web vitals are few standard values that helps you measure the performance of your website. Grafana Faro provides out of the box support to collect Web Vitals using Performance API. The collected data is auto transferred to the Grafana Agent.
  • Tracing: You can enable tracing for set of urls. When tracing is enabled, Faro API will intercept the relevant API calls and inject “traceparent” header into API request. Along with this, it also injects “X-Faro-Session-Id” for session tracking.

Since Grafana Faro is in public review, I expect that more instrumentations will also be introduced with time. You can explore more here.

Here is a coded example of Grafana faro initialization that you can inject into your frontend application:

// GrafanaFaro.ts file 

import {
initializeFaro,
Faro,
ConsoleInstrumentation,
LogLevel,
WebVitalsInstrumentation,
ErrorsInstrumentation,
FetchTransport
} from "@grafana/faro-web-sdk";
import { TracingInstrumentation } from '@grafana/faro-web-tracing';

class GrafanaFaro {
faroInstance: Faro | undefined;
initialize = (
appName: string,
key: string,
user: any,
) => {
try {
if (!this.faroInstance) {
this.faroInstance = initializeFaro({
user: user,
instrumentations: [
new ConsoleInstrumentation({
disabledLevels: [LogLevel.WARN, LogLevel.LOG, LogLevel.DEBUG, LogLevel.INFO]
}),
new ErrorsInstrumentation(),
new WebVitalsInstrumentation(),
new TracingInstrumentation({
instrumentationOptions: {
// Requests to these URLs will have tracing headers attached.
propagateTraceHeaderCorsUrls: [<ENDPOINTS_TO_TRACE_HERE>],
},
}),
],
transports: [
new FetchTransport({
url: <FARO_HOST_URL>,
// Optional, if your receiver requires an API key
apiKey: key,
// Optional, if you want to customize the fetch options
requestOptions: {
headers: {
...
},
...
},
}),
],
session: (window as any).__PRELOADED_STATE__?.faro?.session,
batching: {
enabled: true,
},
app: {
name: appName,
version: '1.0.0',
environment: 'production'
},
});
}
} catch (e) {
console.error('Unable to initialize Faro Agent for Observability');
this.faroInstance = undefined;
} finally {
return this;
}
}
isInitialized = () => !!this.faroInstance;
pushError = (error: Error) => {
this.faroInstance?.api.pushError(error);
}
pushLog = (message: string | number) => {
this.faroInstance?.api.pushLog([message]);
}
pushView = (viewName: string) => {
this.faroInstance?.api.setView({ name: viewName });
}
}

Notice, we used “FetchTransport” here. Faro also provides few transports for customisations. We used FetchTransport in order to intercept Faro Request and pass in our custom header. You can ignore this if this is not of your use.

Once you integrate all this and your application is up and running, you should be able to see the logs being logged onto Loki and your traces being traced onto Tempo of your Grafana instance.

Dashboarding of Logs

Once logs are in-place and working, you can simple put up a dashboard over these logs in order to generate some useful information from these logs. You can import this dashboard by Grafana into your Grafana application.

Before we end

Few things that are worth mentioning and that you may encounter while deploying your own Grafana Faro instance.

  • Inside river file, for security reasons, make sure to provide a proper values to cors_allowed_origins, but not “*”.
  • Make your Faro agent more secure by providing “api_key”.
  • Keep your Faro agent behind any proxy layer, backend service or BFF such that your Faro endpoint is not exposed from client side app.
  • You may need to whitelist custom headers sent by Faro when you enable tracing. Else you may face unexpected CORS errors.

In conclusion, prioritising frontend observability is pivotal for delivering exceptional user experiences. Utilising tools like Grafana, Loki, Tempo, and embracing standards like OpenTelemetry empowers developers to gain deep insights into application performance, troubleshoot issues efficiently, and ultimately enhance the overall quality of web applications. As we celebrate a year of advancements in frontend observability, let’s continue leveraging these tools to shape a more responsive, reliable, and user-friendly digital landscape.

If you think that something is missing or could be better interpreted, please drop a feedback, would love to incorporate. Else 👏 👀

Disclaimer:

Bazaar Technologies believes in sharing knowledge and freedom of expression, and it encourages it’s colleagues and friends to share knowledge, experiences and opinions in written form on it’s medium publication, in a hope that some people across the globe might find the content helpful. However the content shared in this post and other posts on this medium publication mostly describe and highlight the opinions of the authors, which might or might not be the actual and official perspective of Bazaar Technologies.

--

--