Tracing

Mahakam comes with a built-in tracing support. It uses OpenTelemetry to collect traces data.

Example

Here is an example of a tracing middleware.

// main.go
package main

import (
	"context"
	"log"
	"net/http"
	"time"

	"github.com/seiortech/mahakam"
	"github.com/seiortech/mahakam/extensions"
	"github.com/seiortech/mahakam/middleware"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
)

var (
	serviceName  = "example-tracing"
	collectorURL = "jaeger:4317"
)

func main() {
	tracer := extensions.NewTracer(serviceName, collectorURL)

	cleanup := tracer.Init()
	defer cleanup(context.Background())

	mux := http.NewServeMux()

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		_, span := tracer.Tracer().Start(r.Context(), "handle_root")
		defer span.End()

		span.SetAttributes(attribute.String("handler", "root"))
		w.Write([]byte("Hello, World!"))
	})

	mux.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
		ctx, span := tracer.Tracer().Start(r.Context(), "handle_foo")
		defer span.End()

		span.SetAttributes(attribute.String("handler", "foo"))

		fetchData(ctx, tracer.Tracer())
		queryDatabase(ctx, tracer.Tracer())

		w.Write([]byte("foo"))
	})

	mux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
		ctx, span := tracer.Tracer().Start(r.Context(), "handle_bar")
		defer span.End()

		span.SetAttributes(attribute.String("handler", "bar"))

		fetchData(ctx, tracer.Tracer())

		w.Write([]byte("bar"))
	})

	s := mahakam.NewServer("0.0.0.0:8080", mux)
	s.Use(middleware.Logger)
	s.Use(tracer.Middleware)

	if err := s.ListenAndServe(); err != nil {
		log.Fatalln("Failed to start server:", err)
	}
}

func fetchData(ctx context.Context, tracer trace.Tracer) {
	_, span := tracer.Start(ctx, "fetch_data")
	defer span.End()

	span.SetAttributes(
		attribute.String("operation", "fetch_data"),
		attribute.Int("duration_ms", 2000),
	)

	time.Sleep(2 * time.Second)
}

func queryDatabase(ctx context.Context, tracer trace.Tracer) {
	_, span := tracer.Start(ctx, "query_database")
	defer span.End()

	span.SetAttributes(
		attribute.String("operation", "query_database"),
		attribute.String("db.type", "postgres"),
		attribute.Int("duration_ms", 1000),
	)

	time.Sleep(1 * time.Second)
}

now, we need to setup our jaeger and prometheus exporters.

services:
  server:
    build: .
    restart: always
    container_name: server
    platform: linux/aarch64
    ports:
      - "8080:8080"
    environment:
      - OTLP_ENDPOINT=http://jaeger:4318/v1/traces
    depends_on:
      - jaeger

  jaeger:
    image: jaegertracing/all-in-one:1.70.0
    ports:
      - "16686:16686"  # UI
      - "4317:4317"   
    environment:
      - COLLECTOR_OTLP_ENABLED=true
      - LOG_LEVEL=debug
    container_name: jaeger

After that, we need to create a Dockerfile for building our server.

FROM golang:1.24.3-alpine AS build

WORKDIR /app

COPY go.mod ./

RUN go mod download && go mod verify

COPY . .

RUN go build -o /server example/tracing/cmd/main.go

FROM alpine

WORKDIR /app
RUN mkdir -p /app/logs

COPY --from=build /server /app/server

ENTRYPOINT ["/app/server"]

Now, let’s run our docker-compose.

docker-compose up -d

After that, we can access our jaeger dashboard at http://localhost:16686/.

💡 Tip
You can use any other OpenTelemetry compatible monitor like Grafana Tempo or SigNoz.

Implementation

You can check out the Mahakam repository for the tracing example.