Skip to content

Commit ddae343

Browse files
committed
Updating instrumented example
1 parent 38ceebf commit ddae343

File tree

5 files changed

+150
-40
lines changed

5 files changed

+150
-40
lines changed

examples/dice/instrumented/main.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ package main
77

88
import (
99
"context"
10+
"encoding/json"
1011
"errors"
1112
"log"
1213
"net"
1314
"net/http"
1415
"os"
1516
"os/signal"
17+
"strconv"
1618
"time"
1719

1820
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@@ -40,8 +42,12 @@ func run() error {
4042
}()
4143

4244
// Start HTTP server.
45+
port := os.Getenv("APPLICATION_PORT")
46+
if port == "" {
47+
port = "8080"
48+
}
4349
srv := &http.Server{
44-
Addr: ":8080",
50+
Addr: ":" + port,
4551
BaseContext: func(net.Listener) context.Context { return ctx },
4652
ReadTimeout: time.Second,
4753
WriteTimeout: 10 * time.Second,
@@ -80,10 +86,55 @@ func newHTTPHandler() http.Handler {
8086
}
8187

8288
// Register handlers.
83-
handleFunc("/rolldice/", rolldice)
84-
handleFunc("/rolldice/{player}", rolldice)
89+
handleFunc("/rolldice", handleRollDice)
8590

8691
// Add HTTP instrumentation for the whole server.
8792
handler := otelhttp.NewHandler(mux, "/")
8893
return handler
8994
}
95+
96+
func handleRollDice(w http.ResponseWriter, r *http.Request) {
97+
// Parse query parameters
98+
rollsParam := r.URL.Query().Get("rolls")
99+
player := r.URL.Query().Get("player")
100+
101+
// Default rolls = 1 if not defined
102+
if rollsParam == "" {
103+
rollsParam = "1"
104+
}
105+
106+
// Check if rolls is a number
107+
rolls, err := strconv.Atoi(rollsParam)
108+
if err != nil {
109+
w.WriteHeader(http.StatusBadRequest)
110+
msg := "Parameter rolls must be a positive integer"
111+
_ = json.NewEncoder(w).Encode(map[string]string{
112+
"status": "error",
113+
"message": msg,
114+
})
115+
log.Printf("WARN: %s", msg)
116+
return
117+
}
118+
119+
results, err := rollDice(r.Context(), rolls)
120+
if err != nil {
121+
// Signals invalid input (<=0)
122+
w.WriteHeader(http.StatusInternalServerError)
123+
log.Printf("ERROR: %v", err)
124+
return
125+
}
126+
127+
if player == "" {
128+
log.Printf("DEBUG: anonymous player rolled %v", results)
129+
} else {
130+
log.Printf("DEBUG: player=%s rolled %v", player, results)
131+
}
132+
log.Printf("INFO: %s %s -> 200 OK", r.Method, r.URL.String())
133+
134+
w.Header().Set("Content-Type", "application/json")
135+
if len(results) == 1 {
136+
json.NewEncoder(w).Encode(results[0])
137+
} else {
138+
json.NewEncoder(w).Encode(results)
139+
}
140+
}

examples/dice/instrumented/otel.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"go.opentelemetry.io/otel/propagation"
1717
"go.opentelemetry.io/otel/sdk/log"
1818
"go.opentelemetry.io/otel/sdk/metric"
19+
"go.opentelemetry.io/otel/sdk/resource"
1920
"go.opentelemetry.io/otel/sdk/trace"
2021
)
2122

@@ -42,12 +43,19 @@ func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
4243
err = errors.Join(inErr, shutdown(ctx))
4344
}
4445

46+
res, _ := resource.New(ctx,
47+
resource.WithFromEnv(), // reads OTEL_SERVICE_NAME & OTEL_RESOURCE_ATTRIBUTES
48+
resource.WithProcess(),
49+
resource.WithHost(),
50+
resource.WithTelemetrySDK(),
51+
)
52+
4553
// Set up propagator.
4654
prop := newPropagator()
4755
otel.SetTextMapPropagator(prop)
4856

4957
// Set up trace provider.
50-
tracerProvider, err := newtracerProvider()
58+
tracerProvider, err := newtracerProvider(res)
5159
if err != nil {
5260
handleErr(err)
5361
return shutdown, err
@@ -56,7 +64,7 @@ func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
5664
otel.SetTracerProvider(tracerProvider)
5765

5866
// Set up meter provider.
59-
meterProvider, err := newMeterProvider()
67+
meterProvider, err := newMeterProvider(res)
6068
if err != nil {
6169
handleErr(err)
6270
return shutdown, err
@@ -65,7 +73,7 @@ func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
6573
otel.SetMeterProvider(meterProvider)
6674

6775
// Set up logger provider.
68-
loggerProvider, err := newLoggerProvider()
76+
loggerProvider, err := newLoggerProvider(res)
6977
if err != nil {
7078
handleErr(err)
7179
return shutdown, err
@@ -83,43 +91,41 @@ func newPropagator() propagation.TextMapPropagator {
8391
)
8492
}
8593

86-
func newtracerProvider() (*trace.TracerProvider, error) {
87-
traceExporter, err := stdouttrace.New(
88-
stdouttrace.WithPrettyPrint())
94+
func newtracerProvider(res *resource.Resource) (*trace.TracerProvider, error) {
95+
traceExporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
8996
if err != nil {
9097
return nil, err
9198
}
9299

93100
tracerProvider := trace.NewTracerProvider(
94-
trace.WithBatcher(traceExporter,
95-
// Default is 5s. Set to 1s for demonstrative purposes.
96-
trace.WithBatchTimeout(time.Second)),
101+
trace.WithBatcher(traceExporter, trace.WithBatchTimeout(time.Second)), // Default is 5s. Set to 1s for demonstrative purposes.
102+
trace.WithResource(res),
97103
)
98104
return tracerProvider, nil
99105
}
100106

101-
func newMeterProvider() (*metric.MeterProvider, error) {
107+
func newMeterProvider(res *resource.Resource) (*metric.MeterProvider, error) {
102108
metricExporter, err := stdoutmetric.New()
103109
if err != nil {
104110
return nil, err
105111
}
106112

107113
meterProvider := metric.NewMeterProvider(
108-
metric.WithReader(metric.NewPeriodicReader(metricExporter,
109-
// Default is 1m. Set to 3s for demonstrative purposes.
110-
metric.WithInterval(3*time.Second))),
114+
metric.WithReader(metric.NewPeriodicReader(metricExporter, metric.WithInterval(3*time.Second))), // Default is 1m. Set to 3s for demonstrative purposes.
115+
metric.WithResource(res),
111116
)
112117
return meterProvider, nil
113118
}
114119

115-
func newLoggerProvider() (*log.LoggerProvider, error) {
120+
func newLoggerProvider(res *resource.Resource) (*log.LoggerProvider, error) {
116121
logExporter, err := stdoutlog.New()
117122
if err != nil {
118123
return nil, err
119124
}
120125

121126
loggerProvider := log.NewLoggerProvider(
122127
log.WithProcessor(log.NewBatchProcessor(logExporter)),
128+
log.WithResource(res),
123129
)
124130
return loggerProvider, nil
125131
}

examples/dice/instrumented/rolldice.go

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
package main
55

66
import (
7-
"io"
7+
"context"
8+
"errors"
89
"math/rand"
9-
"net/http"
10-
"strconv"
1110

1211
"go.opentelemetry.io/otel"
1312
"go.opentelemetry.io/otel/attribute"
@@ -19,42 +18,95 @@ import (
1918
const name = "go.opentelemetry.io/contrib/examples/dice"
2019

2120
var (
22-
tracer = otel.Tracer(name)
23-
meter = otel.Meter(name)
24-
logger = otelslog.NewLogger(name)
25-
rollCnt metric.Int64Counter
21+
tracer = otel.Tracer(name)
22+
meter = otel.Meter(name)
23+
logger = otelslog.NewLogger(name)
24+
rollCnt metric.Int64Counter
25+
outcomeHist metric.Int64Histogram
26+
lastRollsGauge metric.Int64ObservableGauge
27+
lastRolls int64
2628
)
2729

2830
func init() {
2931
var err error
3032
rollCnt, err = meter.Int64Counter("dice.rolls",
31-
metric.WithDescription("The number of rolls by roll value"),
33+
metric.WithDescription("The number of rolls"),
3234
metric.WithUnit("{roll}"))
3335
if err != nil {
3436
panic(err)
3537
}
38+
39+
outcomeHist, err = meter.Int64Histogram(
40+
"dice.outcome",
41+
metric.WithDescription("Distribution of dice outcomes (1-6)"),
42+
metric.WithUnit("{count}"),
43+
)
44+
if err != nil {
45+
panic(err)
46+
}
47+
48+
lastRollsGauge, err = meter.Int64ObservableGauge(
49+
"dice.last.rolls",
50+
metric.WithDescription("The last rolls value observed"),
51+
)
52+
if err != nil {
53+
panic(err)
54+
}
55+
56+
// Register the gauge callback
57+
_, err = meter.RegisterCallback(
58+
func(ctx context.Context, o metric.Observer) error {
59+
o.ObserveInt64(lastRollsGauge, lastRolls)
60+
return nil
61+
},
62+
lastRollsGauge,
63+
)
64+
if err != nil {
65+
panic(err)
66+
}
3667
}
3768

38-
func rolldice(w http.ResponseWriter, r *http.Request) {
39-
ctx, span := tracer.Start(r.Context(), "roll")
69+
// rollDice is the outer function which Does the error handling.
70+
func rollDice(ctx context.Context, rolls int) ([]int, error) {
71+
ctx, span := tracer.Start(ctx, "rollDice")
4072
defer span.End()
4173

42-
roll := 1 + rand.Intn(6) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive.
74+
if rolls <= 0 {
75+
err := errors.New("rolls must be positive")
76+
span.RecordError(err)
77+
logger.ErrorContext(ctx, "error", "error", err)
78+
return nil, err
79+
}
80+
81+
if rolls == 1 {
82+
val := rollOnce(ctx)
83+
outcomeHist.Record(ctx, int64(val))
84+
lastRolls = int64(rolls)
85+
return []int{val}, nil
86+
}
4387

44-
var msg string
45-
if player := r.PathValue("player"); player != "" {
46-
msg = player + " is rolling the dice"
47-
} else {
48-
msg = "Anonymous player is rolling the dice"
88+
results := make([]int, rolls)
89+
for i := 0; i < rolls; i++ {
90+
results[i] = rollOnce(ctx)
91+
outcomeHist.Record(ctx, int64(results[i]))
4992
}
50-
logger.InfoContext(ctx, msg, "result", roll)
93+
94+
rollsAttr := attribute.Int("rolls", rolls)
95+
span.SetAttributes(rollsAttr)
96+
rollCnt.Add(ctx, 1, metric.WithAttributes(rollsAttr))
97+
lastRolls = int64(rolls)
98+
return results, nil
99+
}
100+
101+
// rollOnce is the inner function — returns a random number 1–6.
102+
func rollOnce(ctx context.Context) int {
103+
ctx, span := tracer.Start(ctx, "rollOnce")
104+
defer span.End()
105+
106+
roll := 1 + rand.Intn(6) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive.
51107

52108
rollValueAttr := attribute.Int("roll.value", roll)
53109
span.SetAttributes(rollValueAttr)
54-
rollCnt.Add(ctx, 1, metric.WithAttributes(rollValueAttr))
55110

56-
resp := strconv.Itoa(roll) + "\n"
57-
if _, err := io.WriteString(w, resp); err != nil {
58-
logger.ErrorContext(ctx, "Write failed", "error", err)
59-
}
111+
return roll
60112
}

examples/dice/uninstrumented/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func handleRollDice(w http.ResponseWriter, r *http.Request) {
9696

9797
results, err := rolldice(rolls)
9898
if err != nil {
99-
// Library signals invalid input (<=0)
99+
// Signals invalid input (<=0)
100100
w.WriteHeader(http.StatusInternalServerError)
101101
log.Printf("ERROR: %v", err)
102102
return

examples/dice/uninstrumented/rolldice.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"math/rand"
99
)
1010

11+
// rolldice is the outer function which does the error handling.
1112
func rolldice(rolls int) ([]int, error) {
1213
if rolls <= 0 {
1314
return nil, errors.New("rolls must be positive")

0 commit comments

Comments
 (0)