Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the rolldice example #1566

Merged
merged 8 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
- Support `golang.org/x/net` `0.34.0`. ([#1552](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1552))
- Support `google.golang.org/grpc` `1.71.0-dev`. ([#1467](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1467))

### Changed

- Update the `rolldice` example to better show the functionality of the project. ([#1566](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1566))

## [v0.19.0-alpha] - 2024-12-05

### Added
Expand Down
7 changes: 0 additions & 7 deletions examples/rolldice/Dockerfile

This file was deleted.

99 changes: 89 additions & 10 deletions examples/rolldice/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,98 @@
# Example of Auto instrumentation of HTTP server
# Roll Dice Application

Rolldice server exposes an endpoint `/rolldice.` When we hit the endpoint, it returns a random number between 1-6.
![Application](./images/app.png)

For testing auto instrumentation, we can use the docker compose.
The `rolldice` application is a simple web application that returns a whole number between 1 and 6 when a user makes a request to `/rolldice/{player}`.

To run the example, bring up the services using the command.
The application is made up of two services: the `frontend` and `user` services.
The `frontend` service handles the request, and the `user` service manages users accounts for the application.

## Run

Run the application using `docker-compose`.

```terminal
docker-compose up
```
docker compose up
```

Now, you can hit roll dice server using the below command
This will start the application and all telemetry related services.
You should see a log line from `go-auto-frontend-1` and `go-auto-user-1` with a message of `"instrumentation loaded successfully, starting..."`. For example,

```terminal
go-auto-user-1 | {"time":"2025-01-09T16:18:08.182081553Z","level":"INFO","source":{"function":"main.main","file":"/app/cli/main.go","line":129},"msg":"instrumentation loaded successfully, starting..."}
```
curl localhost:8080/rolldice

This means the application has loaded and should be ready to explore.

## Explore

With the application running, you can now [visit the `rolldice` UI](http://localhost:8080/rolldice/Alice) as the user `Alice` to start rolling dice.

![Success](./images/success.png)

Refresh the page a few times and you may notice errors are randomly returned.

![Internal error](./images/failure.png)

You can use the observability built into the application and provided by OpenTelemetry Go auto-instrumentation to investigate these errors.

## Observability

Visit the [Jaeger UI](http://localhost:16686/) to see tracing data from the requests you just made.

![Jaeger overview](./images/jaeger_overview.png)

### Context Propagation

Looking at one of the traces within Jaeger we can see spans from both the `frontend` and `user` services.

![Jaeger trace](./images/jaeger_trace.png)

From this
Both services have independently been instrumented with OpenTelemetry Go auto-instrumentation.
Even though the instrumentation for each service is run in a unique process, the trace though them is correctly shown.
Meaning, the trace context is correctly propagated by the auto-instrumentation.
It will parse and use any incoming trace information and will also add any active tracing information to outgoing requests of a service.

### Built in Instrumentation

Both services are instrumented with the OpenTelemetry Go auto-instrumentation in multiple ways.
Primarily, they are instrumented using built-in instrumentation provided by the auto-instrumentation.

Highlighted in the trace, you can see spans from the `net/http` and `database/sql` instrumentation for the Go standard library.

![`net/http` span](./images/jaeger_http.png)

![`database/sql` span](./images/jaeger_db.png)

See the [compatibility documentation](../../COMPATIBILITY.md) for all packages with built-in instrumentation.

### Hybrid Instrumentation

In addition to built-in instrumentation, OpenTelemetry Go auto-instrumentation integrates with the OpenTelemetry API.

The `user` service has been updated to manually use the OpenTelemetry API to investigate errors coming from the problematic `userQuota` function.

```go
tracer := otel.Tracer("user")
ctx, span := tracer.Start(ctx, "userQuota")
u, err := useQuota(ctx, db, name)
if err != nil {
span.SetStatus(codes.Error, "failed to query user quota")
span.RecordError(err)
span.End()
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
span.End()
```
Every hit to the server should generate a trace that we can observe in [Jaeger UI](http://localhost:16686/)

Example trace ![Image](jaeger.jpg)
This span can be seen in the Jaeger UI.

![Jaeger error span](./images/jaeger_error.png)

The OpenTelemetry Go auto-instrumentation automatically captures this span without the `user` service having to setup an SDK.
It is seamlessly integrated into the already active tracing being done by the auto-instrumentation.

This hybrid approach allows you to start small when manually instrumenting your applications.
As can be seen here, it can be useful in troubleshooting or just understanding behavior of your code.
72 changes: 48 additions & 24 deletions examples/rolldice/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,48 +1,72 @@
version: "3.9"

networks:
default:
name: roll
name: dice
driver: bridge

services:
rolldice:
jaeger:
image: jaegertracing/jaeger:2.1.0
ports:
- "16686:16686" # Web HTTP
restart: unless-stopped
user:
build:
context: ./user/
dockerfile: ./Dockerfile
pid: "host"
ports:
- "8082:8082"
volumes:
- /proc:/host/proc
restart: unless-stopped
go-auto-user:
depends_on:
- user
build:
context: ../..
dockerfile: ./Dockerfile
privileged: true
pid: "host"
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318
- OTEL_GO_AUTO_TARGET_EXE=/usr/local/bin/user
- OTEL_SERVICE_NAME=user
- OTEL_PROPAGATORS=tracecontext,baggage
- OTEL_GO_AUTO_GLOBAL=true
- OTEL_GO_AUTO_INCLUDE_DB_STATEMENT=true
- OTEL_GO_AUTO_PARSE_DB_STATEMENT=true
volumes:
- /proc:/host/proc
frontend:
depends_on:
- jaeger
- user
build:
context: .
dockerfile: ./Dockerfile
dockerfile: ./frontend/Dockerfile
command: "-user=http://user:8082"
pid: "host"
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318
- OTEL_SERVICE_NAME=frontend
- OTEL_PROPAGATORS=tracecontext,baggage
ports:
- "8080:8080"
volumes:
- /proc:/host/proc
go-auto:
go-auto-frontend:
depends_on:
- rolldice
- frontend
build:
context: ../..
dockerfile: Dockerfile
dockerfile: ./Dockerfile
privileged: true
pid: "host"
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318
- OTEL_GO_AUTO_TARGET_EXE=/app/main
- OTEL_SERVICE_NAME=rolldice
- OTEL_GO_AUTO_TARGET_EXE=/usr/local/bin/frontend
- OTEL_SERVICE_NAME=frontend
- OTEL_PROPAGATORS=tracecontext,baggage
- OTEL_GO_AUTO_GLOBAL=true
volumes:
- /proc:/host/proc

jaeger:
image: jaegertracing/all-in-one:1.60@sha256:4fd2d70fa347d6a47e79fcb06b1c177e6079f92cba88b083153d56263082135e
ports:
- "16686:16686"
- "14268:14268"
environment:
- COLLECTOR_OTLP_ENABLED=true
- LOG_LEVEL=debug
deploy:
resources:
limits:
memory: 300M
restart: unless-stopped
17 changes: 17 additions & 0 deletions examples/rolldice/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM golang:1.23

WORKDIR /usr/src/user

COPY user/ ./

WORKDIR /usr/src/frontend

COPY frontend/go.mod frontend/go.sum ./
RUN --mount=type=cache,target=/go/pkg go mod download && go mod verify

COPY frontend/ .
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
go build -v -o /usr/local/bin/frontend ./...

ENTRYPOINT ["frontend"]
7 changes: 7 additions & 0 deletions examples/rolldice/frontend/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module go.opentelemetry.io/auto/examples/rolldice/frontend

go 1.23.0

replace go.opentelemetry.io/auto/examples/rolldice/user => ../user/

require go.opentelemetry.io/auto/examples/rolldice/user v0.0.0-00010101000000-000000000000
File renamed without changes.
42 changes: 42 additions & 0 deletions examples/rolldice/frontend/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"context"
"flag"
"log"
"os"
"os/signal"
)

var (
listenAddr = flag.String("addr", ":8080", "server listen address")
userAddr = flag.String("user", "http://localhost:8082", "user service address")
)

func main() {
flag.Parse()

// Handle SIGINT (CTRL+C) gracefully.
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()

srv := newServer(ctx, *listenAddr, *userAddr)
errCh := make(chan error, 1)
go func() { errCh <- srv.ListenAndServe() }()

log.Printf("Frontend server listening at %s ...", *listenAddr)

var err error
select {
case err = <-errCh:
case <-ctx.Done():
err = srv.Shutdown(context.Background())
}
if err != nil {
log.Print("Frontend server error:", err)
}
log.Print("Frontend server stopped.")
}
64 changes: 64 additions & 0 deletions examples/rolldice/frontend/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"context"
"errors"
"io"
"log"
"math/rand"
"net"
"net/http"
"strconv"

"go.opentelemetry.io/auto/examples/rolldice/user"
)

type serviceKeyType int

const userKey serviceKeyType = 0

func newServer(ctx context.Context, listenAddr, userAddr string) *http.Server {
client := user.NewClient(http.DefaultClient, userAddr)
if err := client.HealthCheck(ctx); err != nil {
log.Print("Cannot reach User service: ", err)
} else {
log.Print("Connected to User service at ", userAddr)
}
ctx = context.WithValue(ctx, userKey, client)

mux := http.NewServeMux()
mux.HandleFunc("/rolldice/{player}", rolldice)

return &http.Server{
Addr: listenAddr,
BaseContext: func(_ net.Listener) context.Context { return ctx },
Handler: mux,
}
}

func rolldice(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

player := r.PathValue("player")

client, ok := ctx.Value(userKey).(*user.Client)
if !ok {
http.Error(w, "Internal Error", http.StatusInternalServerError)
return
}

if err := client.UseQuota(ctx, player); err != nil {
if errors.Is(err, user.ErrInsufficient) {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
} else {
http.Error(w, "Internal Error", http.StatusInternalServerError)
}
return
}

roll := 1 + rand.Intn(6)
_, _ = io.WriteString(w, strconv.Itoa(roll)+"\n")
}
3 changes: 0 additions & 3 deletions examples/rolldice/go.mod

This file was deleted.

Binary file added examples/rolldice/images/app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rolldice/images/failure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rolldice/images/jaeger_db.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rolldice/images/jaeger_error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rolldice/images/jaeger_http.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rolldice/images/jaeger_overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rolldice/images/jaeger_trace.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/rolldice/images/success.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed examples/rolldice/jaeger.jpg
Binary file not shown.
Loading
Loading