This time, I will use Jaeger for tracking processes of my Go server. I will use a Jaeger Go library from OpenTelemetry to connect to jaeger collector
Then create a Go module first
mkdir jaeger-tracing
cd jaeger-tracing
go mod init jaeger
Then write a tracer provider
touch provider.go
package main
import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
)
var (
_serviceName = "myservice"
_environment = "production"
)
func NewTraceProvider(url string) (*tracesdk.TracerProvider, error) {
exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
if err != nil {
return nil, err
}
provider := tracesdk.NewTracerProvider(
tracesdk.WithBatcher(exporter),
tracesdk.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(_serviceName),
attribute.String("environment", _environment),
attribute.Int64("ID", 1),
)),
)
return provider, nil
}
This NewTracerProvider
function returns a provider that connects with the Jaeger Collector endpoint with some simple config.
Now, write the main
function to call this function
touch main.go
func main() {
provider, err := NewTraceProvider("http://localhost:14268/api/traces")
if err != nil {
log.Fatal(err)
}
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
if err := provider.Shutdown(ctx); err != nil {
log.Fatal(err)
}
}()
otel.SetTracerProvider(provider)
}
Then start wrting an API server, continue writing the below code in the main
function
func main() {
// ...
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
r := gin.Default()
SetupHandler(r)
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
log.Println("Server is running, press ctrl+C to stop")
<-ctx.Done()
stop()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
if err := provider.Shutdown(ctx); err != nil {
log.Fatal(err)
}
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server is forced to shutdown: ", err)
}
log.Println("Server exiting")
}
Next, implement SetupHandler
function to register an endpoint for the API server
touch controller.go
package main
import (
"context"
"time"
"github.com/gin-gonic/gin"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
func FindItem(ctx context.Context) {
tr := otel.Tracer("component-query")
_, span := tr.Start(ctx, "query")
defer span.End()
span.SetAttributes(attribute.Key("search.id").String("a1b2c3d4"))
// do something
time.Sleep(time.Millisecond * 500)
}
func Filter(ctx context.Context) {
tr := otel.Tracer("component-filter")
_, span := tr.Start(ctx, "filter")
defer span.End()
// do something
time.Sleep(time.Millisecond * 100)
// code example when there is an error
// span.SetStatus(codes.Error, "fail to filter items")
// span.RecordError(errors.New("item format is not correct"))
time.Sleep(time.Millisecond * 200)
}
func SetupHandler(r *gin.Engine) {
r.GET("/", func(c *gin.Context) {
tr := otel.Tracer("component-http-request")
ctx, span := tr.Start(context.Background(), "http-request")
FindItem(ctx)
Filter(ctx)
defer span.End()
})
}
From the above code, I have one endpoint which is the root path (GET "/"). When there is a request coming, I create a tracer named component-http-request
and then start the span while running two functions (FindItem
and Filter
). In those two functions, I wrote sleep functions to mock API behavior.
The functions also create tracers that would be childrens of the main tracer (component-http-request
).
Before starting the server, let's prepare Jaeger with docker and then run the Go server
docker run -d -p 14268:14268 -p 16686:16686 jaegertracing/all-in-one
go run .
You can visit jaeger webserver in localhost:16686
. In the webserver, at the form in the left side, set Service
to myservice
and click Find Traces
.
There is no traces to inspect yet; let's add some by doing http request to the Go server
curl localhost:8080
Then refresh the jaeger webserver and you will see a new trace has been added. Click the trace to inspect and see what happened.
There is a bar graph that tells about when an event was started and when it was ended. If there is error, it can be used for debugging since it tells when and where the error happened; this would reduce your time to find the bug.
sources