containers

The main focus of this article is to demonstrate how to instrument React applications to make them observable. For a good overview of observability and OpenTelemetry, please take a look at the article, Observability in 2022: Why it matters and how OpenTelemetry can help.

    10-step OpenTelemetry demonstration

    Related to the OpenTelemetry, we are using the following:

    • Auto instrumentation via sdk-trace-web and a plugin to provide auto instrumentation for fetch.
    • OpenTelemetry Collector (also known as OTELCOL).
    • Jaeger
    • Basic collector deployment pattern. For more information about OTELCOL deployment patterns, please take a look at OpenTelemetry Collector Deployment Patterns.

    Step 1. Set up prerequisites

    In this demo, we are going to use Docker and docker-compose. You can refer to Docker and docker-compose to learn more.

    Step 2. Run the React application example

    You will be using a front-end react application that contains the sample React application code that we will instrument. Please note that the repository also contains an Express application as a back end, but the focus of this tutorial is to instrument the front end only.

    The front-end application contains a button that calls the back end using Express and a scroll component that calls the https://randomuser.me/ free public API. We are going to delegate to OpenTelemetry libraries the job of capturing traces for the button and the scroll component. So every time the user clicks on the button or scrolls the page, the auto-instrumentation plugin will generate traces for this.

    Clone the following GitHub repository from the command line:

    git clone https://github.com/obs-nebula/frontend-react.git

    Step 3. Instrument the React application

    The following list shows the dependencies we added. You may want to use newer versions, depending on when you are reading this article:

    "@opentelemetry/exporter-trace-otlp-http": "^0.35.0",
    
    "@opentelemetry/instrumentation": "^0.35.0",
    
    "@opentelemetry/instrumentation-fetch": "^0.35.0",
    
    "@opentelemetry/resources": "^1.9.1",
    
    "@opentelemetry/sdk-trace-web": "^1.8.0",
    
    "@opentelemetry/semantic-conventions": "^1.9.1"

    Create a file named tracing.js that will load OpenTelemetry. We are going to share more details in the following subsections. The content of the front-end/src/tracing.js file is as follows:

    const { Resource } = require('@opentelemetry/resources');
    
    const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
    
    const { WebTracerProvider, SimpleSpanProcessor, ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-web');
    
    const { OTLPTraceExporter }  = require('@opentelemetry/exporter-trace-otlp-http');
    
    const { registerInstrumentations } = require('@opentelemetry/instrumentation');
    
    const { FetchInstrumentation } = require('@opentelemetry/instrumentation-fetch');
    
    const consoleExporter = new ConsoleSpanExporter();
    
    const collectorExporter = new OTLPTraceExporter({
    
      headers: {}
    
    });
    
    const provider = new WebTracerProvider({
    
      resource: new Resource({
    
        [SemanticResourceAttributes.SERVICE_NAME]: process.env.REACT_APP_NAME
    
      })
    
    });
    
    const fetchInstrumentation = new FetchInstrumentation({});
    
    fetchInstrumentation.setTracerProvider(provider);
    
    provider.addSpanProcessor(new SimpleSpanProcessor(consoleExporter));
    
    provider.addSpanProcessor(new SimpleSpanProcessor(collectorExporter));
    
    provider.register();
    
    registerInstrumentations({
    
      instrumentations: [
    
        fetchInstrumentation
    
      ],
    
      tracerProvider: provider
    
    });
    
    export default function TraceProvider ({ children }) {
    
      return (
    
       <>
    
          {children}
    
       </>
    
      );
    
    }

    Step 4. Import the required modules

    Next, you will need to import the OpenTelemetry modules. As you can see, we can get the ConsoleSpanExporter and SimpleSpanProcessor from the @opentelemetry/sdk-trace-web so we don’t need to add an extra dependency for this.

    const { Resource } = require('@opentelemetry/resources');
    
    const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
    
    const { WebTracerProvider,SimpleSpanProcessor, ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-web');
    
    const { OTLPTraceExporter }  = require('@opentelemetry/exporter-trace-otlp-http');
    
    const { registerInstrumentations } = require('@opentelemetry/instrumentation');
    
    const { FetchInstrumentation } = require('@opentelemetry/instrumentation-fetch');

    Step 5. Initialize the tracer

    Since we are using React with JavaScript, to initialize the OpenTelemetry tracer, you will need to create a new instance of the TraceProvider and pass it as a property to your root React component. You can do this by adding the following code to your main application file, in our case, that is in index.js file.

    import TraceProvider from './tracing';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    
    root.render(
    
      <TraceProvider>
    
        <App />
    
      </TraceProvider>
    
    );

    Step 6. Create an OTELCOL exporter instance

    To export the traces to OTELCOL, you will need to create an instance of OTLPTraceExporter.

    Note that we are adding a workaround to use XHR instead of sendBeacon, as described in this OpenTelemetry JS upstream issue. With that, we can fix the CORS problem when exporting.

    const collectorExporter = new OTLPTraceExporter({
    
    headers: {}
    
    });

    Step 7. Create the otel-collector-config file

    Now let’s take a look at the yaml file content. To configure the OTELCOL, you will need to create a new file called otel-collector-config.yaml in your root directory. In this file, we are going to configure the receiver, processor, and exporters (using Jaeger and logging as exporters).

    receivers:
    
      otlp:
    
        protocols:
    
          http:
    
            cors:
    
              allowed_origins: ["*"]
    
              allowed_headers: ["*"]
    
    exporters:
    
      logging:
    
        verbosity: Detailed
    
      jaeger:
    
        endpoint: jaeger-all-in-one:14250
    
        tls:
    
          insecure: true
    
    processors:
    
      batch:
    
    service:
    
      telemetry:
    
        logs:
    
          level: "debug"
    
      pipelines:
    
        traces:
    
          receivers: [otlp]
    
          exporters: [logging, jaeger]
    
          processors: [batch]

    Step 8. Create a docker compose file

    Create a docker-compose file and define the services for OTELCOL, Jaeger, and the application as follows:

    version: "2"
    
    services:
    
      front-end:
    
        build:
    
          context:./front-end
    
        depends_on:
    
          - express-server
    
        ports:
    
          - "3000:3000"
    
        env_file:
    
         -./front-end/src/.env
    
      express-server:
    
        build:
    
          context:./express-server
    
        ports:
    
          - "5000:5000"
    
      collector:
    
        image: otel/opentelemetry-collector:latest
    
        command: ["--config=/otel-collector-config.yaml"]
    
        volumes:
    
          - './otel-collector-config.yaml:/otel-collector-config.yaml'
    
        ports:
    
          - "4318:4318"
    
        depends_on:
    
          - jaeger-all-in-one
    
       # Jaeger
    
      jaeger-all-in-one:
    
        hostname: jaeger-all-in-one
    
        image: jaegertracing/all-in-one:latest
    
        ports:
    
          - "16685"
    
          - "16686:16686"
    
          - "14268:14268"
    
          - "14250:14250"

    Step 9. Start the services

    Once you have created OTELCOL config and Docker compose files, you can start the services by running the following command in your terminal:

    $docker-compose up

    Once the services are started you can access the react application at http://localhost:3000.

    As mentioned previously, every time the users scroll the page, it will generate random data through an API call and the OpenTelemetry will generate traces based on the scrolling activity.

    Step 10. View the traces in Jaeger

    You can view the traces in the Jaeger UI by navigating to http://localhost:16686. The horizontal circles in the chart represent button clicks, and the vertical circles represent scrolling activity, as shown in Figure 1.

    The jaeger UI showing circles in the chart that represent button clicks and scrolling activity.
    Figure 1: Application tracing in Jaeger.

    Let's click on one of the horizontal items and expand the trace detail as shown in Figure 2:

    In the Jaeger UI, expanded trace detail shows the Express back-end was called and the OpenTelemetry library name.
    Figure 2: The expanded trace detail shows the Express back-end was called and the OpenTelemetry library name.

    We can see in Figure 2 that the Express back end was called, and the OpenTelemetry library name that is responsible for this trace generation.

    Now let’s click in one vertical circle as shown in Figure 3:

    The trace information showing that the scrolling activity is calling an external API.
    Figure 3: The trace information showing that the scrolling activity is calling an external API.

    We can see in Figure 3 that scrolling activity is calling an external API.

    Collecting and analyzing telemetry data

    You have successfully enabled OpenTelemetry in your React application using the OpenTelemetry collector and Jaeger. You can now start collecting and analyzing telemetry data. You can use the Jaeger UI to view traces, identify performance bottlenecks, and gain deeper understanding about what the React application is doing when calling external systems.

    Further reading

    Want to learn more about observability and OpenTelemetry? Check out these articles:

    Last updated: August 14, 2023