Elastiflix/go-favorite-elastic-manual/main.go (158 lines of code) (raw):

package main import ( "log" "net/http" "os" "time" "github.com/go-redis/redis/v8" apmgoredis "go.elastic.co/apm/module/apmgoredisv8/v2" "github.com/sirupsen/logrus" "github.com/gin-gonic/gin" "go.elastic.co/apm/v2" "go.elastic.co/apm/module/apmgin/v2" "go.elastic.co/apm/module/apmlogrus/v2" "strconv" "math/rand" ) 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.WithFields(apmlogrus.TraceContext(c.Request.Context())) } 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")) logrus.AddHook(&apmlogrus.Hook{}) 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" } serviceName := os.Getenv("ELASTIC_APM_SERVICE_NAME") if serviceName == "" { serviceName = "python-favorite-elastic-manual" } secretToken := os.Getenv("ELASTIC_APM_SECRET_TOKEN") if secretToken == "" { log.Fatal("ELASTIC_APM_SECRET_TOKEN environment variable not set") } serverURL := os.Getenv("ELASTIC_APM_SERVER_URL") if serverURL == "" { log.Fatal("ELASTIC_APM_SERVER_URL environment variable not set") } // Initialize Redis client rdb := redis.NewClient(&redis.Options{ Addr: redisHost + ":" + redisPort, Password: "", DB: 0, }) rdb.AddHook(apmgoredis.NewHook()) // Initialize router r := gin.New() r.Use(apmgin.Middleware(r)) r.Use(logrusMiddleware) // 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(rand.NormFloat64()*float64(delayTime / 10)+float64(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) { // artificial sleep for delayTime time.Sleep(time.Duration(rand.NormFloat64()*float64(delayTime / 10)+float64(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") tx := apm.TransactionFromContext(c.Request.Context()) tx.Context.SetLabel("quiz_solution", "correlations") tx.Context.SetLabel("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 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") }