Exploring OpenTelemetry and Jaeger
Tags: dotnet, docker, jaeger, opentelemetry
Published: 2023-05-14 09:30:25

Have you followed my tutorial regarding the development of forum-based web applications? I recommend following the series before we start this tutorial. You can check the applications in this repository.

{% github bervProject/forum-api-microservices %}

## Preparation

1. You can start from this [commit](https://github.com/bervProject/forum-api-microservices/tree/951bf85760f2be26d13fafde46573d1ea9b46973).
2. Please check this [pull request](https://github.com/bervProject/forum-api-microservices/pull/12) for the changes.
3. In short, you only need [Docker](https://docs.docker.com/get-docker/) and the source codes in this tutorial.

## Overview


![Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ur4h3cci7oolyws0jz7i.png)

In short, we will call the tracing API in Jaeger from `AuthService`, `ThreadService`, and `UserService`. `ThreadService` and `UserService` will call in port `14268`, but `AuthService` will call in port `4318`. `ThreadService` and `UserService` will use the Jaeger exporter library, but `AuthService` will use the OTLP exporter library.

## Let's Coding

1. We will add Jaeger in our previous `docker-compose.yml`. You can choose to expose all the ports or just expose the UI port, which is `16686`. We will communicate with the internal network of containers.

    ```yml
      jaeger:
        image: jaegertracing/all-in-one:1.45
        networks:
          - backend
        environment:
          - COLLECTOR_ZIPKIN_HTTP_PORT=:9411
          - COLLECTOR_OTLP_ENABLED=true
        ports:
          - 6831:6831/udp
          - 6832:6832/udp
          - 5778:5778
          - 16686:16686
          - 14268:14268
          - 14269:14269
          - 14250:14250
          - 9411:9411
          - 4317:4317
          - 4318:4318
    ```

2. Add these packages to `ThreadService` and `UserService`.

    ```xml
    <PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.4.0" />
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.4.0" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc9.14" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc9.14" />
    ```

3. Add these codes to the `UserService` (`Program.cs`).

    ```csharp
    using System.Diagnostics;
    
    using OpenTelemetry.Exporter;
    using OpenTelemetry.Resources;
    using OpenTelemetry.Trace;


    builder.Services.AddOpenTelemetry()
        .WithTracing(tracerProviderBuilder =>
            tracerProviderBuilder
            
    .AddSource(DiagnosticsConfig.ActivitySource.Name)
                .ConfigureResource(resource => resource
                
    .AddService(DiagnosticsConfig.ServiceName))
                .AddHttpClientInstrumentation()
                .AddAspNetCoreInstrumentation()
                .AddJaegerExporter());

    // after UseCors()
    app.Use(async (context, next) =>
    {
        context.Response.Headers.Add("trace-id", Activity.Current?.TraceId.ToString());
        await next();
    });
    // before MapGet("/users", ...

    // after app.Run()
    public static class DiagnosticsConfig
    {
       public const string ServiceName = "UserService";
       public static ActivitySource ActivitySource = new ActivitySource(ServiceName);
    }
    ```

3. Add these codes to the `ThreadService` (`Program.cs`). The changes are the same with `UserService`. The difference is the `ServiceName`.

    ```csharp
    using System.Diagnostics;
    
    using OpenTelemetry.Exporter;
    using OpenTelemetry.Resources;
    using OpenTelemetry.Trace;


    builder.Services.AddOpenTelemetry()
        .WithTracing(tracerProviderBuilder =>
            tracerProviderBuilder
            
    .AddSource(DiagnosticsConfig.ActivitySource.Name)
                .ConfigureResource(resource => resource
                
    .AddService(DiagnosticsConfig.ServiceName))
                .AddHttpClientInstrumentation()
                .AddAspNetCoreInstrumentation()
                .AddJaegerExporter());

    // after UseCors()
    app.Use(async (context, next) =>
    {
        context.Response.Headers.Add("trace-id", Activity.Current?.TraceId.ToString());
        await next();
    });
    // before MapGet("/users", ...

    // after app.Run()
    public static class DiagnosticsConfig
    {
       public const string ServiceName = "ThreadService";
       public static ActivitySource ActivitySource = new ActivitySource(ServiceName);
    }
    ```
4. Update the `package.json` of `AuthService`. Please run `yarn` to update the `yarn.lock`.

    ```json
    "scripts": {
        "start": "node --require ./lib/instrumentation.js lib/index.js",
        "start:dev": "ts-node-dev --require ./instrumentation.ts src/index.ts",
        "compile": "tsc",
        "lint": "eslint .",
        "fix": "eslint . --fix"
      },
      "dependencies": {
        "@opentelemetry/api": "^1.4.1",
        "@opentelemetry/exporter-metrics-otlp-proto": "^0.39.1",
        "@opentelemetry/exporter-trace-otlp-proto": "^0.39.1",
        "@opentelemetry/instrumentation-express": "^0.32.2",
        "@opentelemetry/instrumentation-http": "^0.39.1",
        "@opentelemetry/sdk-metrics": "^1.13.0",
        "@opentelemetry/sdk-node": "^0.39.1",
    
    ```

5. Update `index.ts` in the `AuthService`.

    ```typescript
    import api from '@opentelemetry/api';

      // add these before logger.info("Getting Request", requestMeta);
      const activeSpan = api.trace.getSpan(api.context.active());
  res.header("trace-id", activeSpan?.spanContext().traceId);
    ```

6. Add `intrumentation.ts` in the `AuthService`.

    ```typescript
    import { NodeSDK } from "@opentelemetry/sdk-node";
    import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
    import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
    import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
    import { Resource } from "@opentelemetry/resources";

    const jaegerHost = process.env.JAEGER_URI || "http://localhost:4318";
    
    const resource =
        Resource.default().merge(
            new Resource({
                
    [SemanticResourceAttributes.SERVICE_NAME]: "AuthService",
            })
        );

    const sdk = new NodeSDK({
        resource: resource,
        traceExporter: new OTLPTraceExporter({
            url: `${jaegerHost}/v1/traces`,
        }),
        instrumentations: [
            new HttpInstrumentation(),
            new ExpressInstrumentation(),
        ]
    });
    
    sdk.start()
    ```

7. Update the `docker-compose.yml` for `thread-services` and `user-services` section.

    ```yml
    depends_on:
      - jaeger
      - mongodb
      - redis
    environment:
        # add these envs
      OTEL_EXPORTER_JAEGER_ENDPOINT: http://jaeger:14268/api/traces
      OTEL_EXPORTER_JAEGER_AGENT_HOST: jaeger
    ```

8. Update the `docker-compose.yml` for `auth-services` section.

    ```yml
    depends_on:
      - jaeger
      - mongodb
      - redis
    environment:
       JAEGER_URI: http://jaeger:4318
    ```
9. Let's build the docker image for those services (`AuthService`, `ThreadService` and `UserService`). You can use `docker compose build`.

10. Start the services. `docker compose up -d`. Ensure all the services are running. `docker compose ps -a`.

    ![docker compose ps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/roegg76rzrjqehojb797.png)
    ![docker desktop](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f1d19a6dpa9ix26ls47u.png)

11. You can start to call the APIs. So you can see the tracing.

    ![jaeger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7kb448ypl3whs2ve5vv3.png)
    ![jaeger-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wlafc73hl4q4zmxowhdn.png)

12. I recommend exploring the tracing when creating the thread and login.

## Thank you

Thank you for reading. If you have any feedback, feel free to comment here.

![GIF Hello](https://media.giphy.com/media/888R35MJTmDxQfRzfS/giphy.gif)