Elastiflix/go-favorite-otel-manual/main.go (203 lines of code) (raw):

package main import ( "log" "net/http" "os" "time" "context" "github.com/go-redis/redis/v8" "github.com/go-redis/redis/extra/redisotel/v8" "github.com/sirupsen/logrus" "github.com/gin-gonic/gin" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" "google.golang.org/grpc/credentials" "crypto/tls" //"go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" //semconv "go.opentelemetry.io/otel/semconv/v1.17.0" //"go.opentelemetry.io/otel/sdk" "go.opentelemetry.io/otel/trace" "strings" "strconv" "math/rand" "go.opentelemetry.io/otel/codes" ) var tracer trace.Tracer func initTracer() func(context.Context) error { tracer = otel.Tracer("go-favorite-otel-manual") // remove https:// from the collector URL if it exists collectorURL = strings.Replace(collectorURL, "https://", "", 1) //serviceVersion := "v1.0.0" secureOption := otlptracegrpc.WithInsecure() // split otlpHeaders by comma and convert to map headers := make(map[string]string) for _, header := range strings.Split(otlpHeaders, ",") { headerParts := strings.Split(header, "=") if len(headerParts) == 2 { headers[headerParts[0]] = headerParts[1] } } exporter, err := otlptrace.New( context.Background(), otlptracegrpc.NewClient( secureOption, otlptracegrpc.WithEndpoint(collectorURL), otlptracegrpc.WithHeaders(headers), otlptracegrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{})), ), ) if err != nil { log.Fatal(err) } otel.SetTracerProvider( sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), //sdktrace.WithResource(resources), ), ) otel.SetTextMapPropagator( propagation.NewCompositeTextMapPropagator( propagation.Baggage{}, propagation.TraceContext{}, ), ) return exporter.Shutdown } var ( collectorURL = os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") otlpHeaders = os.Getenv("OTEL_EXPORTER_OTLP_HEADERS") ) var logger = &logrus.Logger{ Out: os.Stderr, Hooks: make(logrus.LevelHooks), Level: logrus.InfoLevel, Formatter: &logrus.JSONFormatter{ FieldMap: logrus.FieldMap{ logrus.FieldKeyTime: "@timestamp", logrus.FieldKeyLevel: "log.level", logrus.FieldKeyMsg: "message", logrus.FieldKeyFunc: "function.name", // non-ECS }, TimestampFormat: time.RFC3339Nano, }, } func contextLogger(c *gin.Context) logrus.FieldLogger { return logger } func logrusMiddleware(c *gin.Context) { start := time.Now() method := c.Request.Method path := c.Request.URL.Path if rawQuery := c.Request.URL.RawQuery; rawQuery != "" { path += "?" + rawQuery } c.Next() status := c.Writer.Status() contextLogger(c).Infof("%s %s %d %s", method, path, status, time.Since(start)) } func main() { delayTime, _ := strconv.Atoi(os.Getenv("TOGGLE_SERVICE_DELAY")) cleanup := initTracer() defer cleanup(context.Background()) redisHost := os.Getenv("REDIS_HOST") if redisHost == "" { redisHost = "localhost" } redisPort := os.Getenv("REDIS_PORT") if redisPort == "" { redisPort = "6379" } applicationPort := os.Getenv("APPLICATION_PORT") if applicationPort == "" { applicationPort = "5000" } // Initialize Redis client rdb := redis.NewClient(&redis.Options{ Addr: redisHost + ":" + redisPort, Password: "", DB: 0, }) rdb.AddHook(redisotel.NewTracingHook()) // Initialize router r := gin.New() r.Use(logrusMiddleware) r.Use(otelgin.Middleware("go-favorite-otel-manual")) // Define routes r.GET("/", func(c *gin.Context) { contextLogger(c).Infof("Main request successful") c.String(http.StatusOK, "Hello World!") }) r.GET("/favorites", func(c *gin.Context) { // artificial sleep for delayTime time.Sleep(time.Duration(delayTime) * time.Millisecond) userID := c.Query("user_id") contextLogger(c).Infof("Getting favorites for user %q", userID) favorites, err := rdb.SMembers(c.Request.Context(), userID).Result() if err != nil { contextLogger(c).Error("Failed to get favorites for user %q", userID) c.String(http.StatusInternalServerError, "Failed to get favorites") return } contextLogger(c).Infof("User %q has favorites %q", userID, favorites) c.JSON(http.StatusOK, gin.H{ "favorites": favorites, }) }) r.POST("/favorites", func(c *gin.Context) { // start otel span ctx := c.Request.Context() ctx, span := tracer.Start(ctx, "add_favorite_movies") defer span.End() // artificial sleep for delayTime time.Sleep(time.Duration(delayTime) * time.Millisecond) userID := c.Query("user_id") contextLogger(c).Infof("Adding or removing favorites for user %q", userID) var data struct { ID int `json:"id"` } if err := c.BindJSON(&data); err != nil { contextLogger(c).Error("Failed to decode request body for user %q", userID) c.String(http.StatusBadRequest, "Failed to decode request body") return } redisResponse := rdb.SRem(c.Request.Context(), userID, data.ID) if redisResponse.Err() != nil { contextLogger(c).Error("Failed to remove movie from favorites for user %q", userID) c.String(http.StatusInternalServerError, "Failed to remove movie from favorites") return } if redisResponse.Val() == 0 { rdb.SAdd(c.Request.Context(), userID, data.ID) } favorites, err := rdb.SMembers(c.Request.Context(), userID).Result() contextLogger(c).Infof("Getting favorites for user") if err != nil { contextLogger(c).Error("Failed to get favorites for user %q", userID) c.String(http.StatusInternalServerError, "Failed to get favorites") return } contextLogger(c).Infof("User %q has favorites %q", userID, favorites) // if enabled, in 50% of the cases, sleep for 2 seconds sleepTimeStr := os.Getenv("TOGGLE_CANARY_DELAY") sleepTime := 0 if sleepTimeStr != "" { sleepTime, _ = strconv.Atoi(sleepTimeStr) } if sleepTime > 0 && rand.Float64() < 0.5 { time.Sleep(time.Duration(rand.NormFloat64()*float64(sleepTime / 10)+float64(sleepTime))* time.Millisecond) // add label to transaction logger.Info("Canary enabled") span := trace.SpanFromContext(c.Request.Context()) span.SetAttributes(attribute.String("quiz_solution", "correlations")) span.SetAttributes(attribute.String("canary", "test-new-feature")) // read env var TOGGLE_CANARY_FAILURE, which is a float between 0 and 1 if toggleCanaryFailureStr := os.Getenv("TOGGLE_CANARY_FAILURE"); toggleCanaryFailureStr != "" { toggleCanaryFailure, err := strconv.ParseFloat(toggleCanaryFailureStr, 64) if err != nil { toggleCanaryFailure = 0 } if rand.Float64() < toggleCanaryFailure { // throw an exception in 50% of the cases span.SetStatus(codes.Error, "Something went wrong") logger.Error("Something went wrong") panic("Something went wrong") } } } c.JSON(http.StatusOK, gin.H{ "favorites": favorites, }) }) // Start server logger.Infof("App startup") log.Fatal(http.ListenAndServe(":"+applicationPort, r)) logger.Infof("App stopped") }