-
Notifications
You must be signed in to change notification settings - Fork 706
Updating dice example based on Updated Specification for the reference application
#8099
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
base: main
Are you sure you want to change the base?
Changes from all commits
f89e2b5
a337ba6
da2e8cd
08b7f61
6697966
ddf3c68
ccea55d
fbacc37
a474139
6d00f40
c066c03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,10 +4,13 @@ | |
| package main | ||
|
|
||
| import ( | ||
| "io" | ||
| "math/rand" | ||
| "context" | ||
| "encoding/json" | ||
| "errors" | ||
| "math/rand/v2" | ||
| "net/http" | ||
| "strconv" | ||
| "sync/atomic" | ||
|
|
||
| "go.opentelemetry.io/otel" | ||
| "go.opentelemetry.io/otel/attribute" | ||
|
|
@@ -19,42 +22,136 @@ | |
| const name = "go.opentelemetry.io/contrib/examples/dice" | ||
|
|
||
| var ( | ||
| tracer = otel.Tracer(name) | ||
| meter = otel.Meter(name) | ||
| logger = otelslog.NewLogger(name) | ||
| rollCnt metric.Int64Counter | ||
| tracer = otel.Tracer(name) | ||
| meter = otel.Meter(name) | ||
| logger = otelslog.NewLogger(name) | ||
| rollCnt metric.Int64Counter | ||
| outcomeHist metric.Int64Histogram | ||
| lastRollsGauge metric.Int64ObservableGauge | ||
| lastRolls atomic.Int64 | ||
| ) | ||
|
|
||
| func init() { | ||
| var err error | ||
| rollCnt, err = meter.Int64Counter("dice.rolls", | ||
| metric.WithDescription("The number of rolls by roll value"), | ||
| metric.WithDescription("The number of rolls"), | ||
| metric.WithUnit("{roll}")) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| outcomeHist, err = meter.Int64Histogram( | ||
| "dice.outcome", | ||
| metric.WithDescription("Distribution of dice outcomes (1-6)"), | ||
| metric.WithUnit("{count}"), | ||
| ) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| lastRollsGauge, err = meter.Int64ObservableGauge( | ||
| "dice.last.rolls", | ||
| metric.WithDescription("The last rolls value observed"), | ||
| ) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| // Register the gauge callback. | ||
| _, err = meter.RegisterCallback( | ||
| func(ctx context.Context, o metric.Observer) error { | ||
| o.ObserveInt64(lastRollsGauge, lastRolls.Load()) | ||
| return nil | ||
| }, | ||
| lastRollsGauge, | ||
| ) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| } | ||
|
|
||
| func rolldice(w http.ResponseWriter, r *http.Request) { | ||
| ctx, span := tracer.Start(r.Context(), "roll") | ||
| defer span.End() | ||
| func handleRolldice(w http.ResponseWriter, r *http.Request) { | ||
| const maxRolls = 1000 // Arbitrary limit to prevent Slice memory allocation with excessive size value. | ||
|
|
||
| // Parse query parameters. | ||
| rollsParam := r.URL.Query().Get("rolls") | ||
| player := r.URL.Query().Get("player") | ||
|
|
||
| // Default rolls = 1 if not defined. | ||
| if rollsParam == "" { | ||
| rollsParam = "1" | ||
| } | ||
|
|
||
| // Check if rolls is a number. | ||
| rolls, err := strconv.Atoi(rollsParam) | ||
| if err != nil { | ||
| w.Header().Set("Content-Type", "application/json") | ||
| w.WriteHeader(http.StatusBadRequest) | ||
| msg := "Parameter rolls must be a positive integer" | ||
| _ = json.NewEncoder(w).Encode(map[string]string{ | ||
| "status": "error", | ||
| "message": msg, | ||
| }) | ||
| logger.WarnContext(r.Context(), msg) | ||
| return | ||
| } | ||
|
|
||
| results, err := rollDice(r.Context(), rolls) | ||
| if err != nil || rolls > maxRolls { | ||
| // Signals invalid input (<=0). | ||
| w.WriteHeader(http.StatusInternalServerError) | ||
| logger.ErrorContext(r.Context(), err.Error()) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the condition
|
||
| return | ||
| } | ||
|
|
||
| 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. | ||
| if player == "" { | ||
| logger.DebugContext(r.Context(), "anonymous player rolled", "results", results) | ||
| } else { | ||
| logger.DebugContext(r.Context(), "player", player, "results", results) | ||
| } | ||
| logger.InfoContext(r.Context(), "Some player rolled a dice.") | ||
|
|
||
| var msg string | ||
| if player := r.PathValue("player"); player != "" { | ||
| msg = player + " is rolling the dice" | ||
| w.Header().Set("Content-Type", "application/json") | ||
| if len(results) == 1 { | ||
| json.NewEncoder(w).Encode(results[0]) | ||
| } else { | ||
| msg = "Anonymous player is rolling the dice" | ||
| json.NewEncoder(w).Encode(results) | ||
| } | ||
| } | ||
|
|
||
| // rollDice is the outer function which Does the error handling. | ||
| func rollDice(ctx context.Context, rolls int) ([]int, error) { | ||
| ctx, span := tracer.Start(ctx, "rollDice") | ||
| defer span.End() | ||
|
|
||
| if rolls <= 0 { | ||
| err := errors.New("rolls must be positive") | ||
| span.RecordError(err) | ||
| return nil, err | ||
| } | ||
|
|
||
| results := make([]int, rolls) | ||
Check failureCode scanning / CodeQL Slice memory allocation with excessive size value High
This memory allocation depends on a
user-provided value Error loading related location Loading |
||
| for i := 0; i < rolls; i++ { | ||
| results[i] = rollOnce(ctx) | ||
| outcomeHist.Record(ctx, int64(results[i])) | ||
| } | ||
| logger.InfoContext(ctx, msg, "result", roll) | ||
|
|
||
| rollsAttr := attribute.Int("rolls", rolls) | ||
| span.SetAttributes(rollsAttr) | ||
| rollCnt.Add(ctx, 1, metric.WithAttributes(rollsAttr)) | ||
flc1125 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| lastRolls.Store(int64(rolls)) | ||
| return results, nil | ||
| } | ||
|
|
||
| // rollOnce is the inner function — returns a random number 1–6. | ||
| func rollOnce(ctx context.Context) int { | ||
| ctx, span := tracer.Start(ctx, "rollOnce") | ||
| defer span.End() | ||
|
|
||
| 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. | ||
|
|
||
| rollValueAttr := attribute.Int("roll.value", roll) | ||
| span.SetAttributes(rollValueAttr) | ||
| rollCnt.Add(ctx, 1, metric.WithAttributes(rollValueAttr)) | ||
|
|
||
| resp := strconv.Itoa(roll) + "\n" | ||
| if _, err := io.WriteString(w, resp); err != nil { | ||
| logger.ErrorContext(ctx, "Write failed", "error", err) | ||
| } | ||
| return roll | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about Content-Type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thought of leaving it to the defaults as it's a simple app and not explicitly specified in the reference. But I will set
application/jsonas that's how the app responds.