diff --git a/apps/checker/cmd/main.go b/apps/checker/cmd/main.go index 31fa75d06..6f7d4a084 100644 --- a/apps/checker/cmd/main.go +++ b/apps/checker/cmd/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "fmt" "net/http" "os" @@ -11,7 +12,9 @@ import ( "github.com/gin-gonic/gin" "github.com/openstatushq/openstatus/apps/checker" + "github.com/openstatushq/openstatus/apps/checker/pkg/logger" "github.com/openstatushq/openstatus/apps/checker/request" + "github.com/rs/zerolog/log" ) func main() { @@ -29,6 +32,9 @@ func main() { // environment variables. flyRegion := env("FLY_REGION", "local") cronSecret := env("CRON_SECRET", "") + logLevel := env("LOG_LEVEL", "warn") + + logger.Configure(logLevel) httpClient := &http.Client{} defer httpClient.CloseIdleConnections() @@ -36,7 +42,6 @@ func main() { router := gin.New() router.POST("/checker", func(c *gin.Context) { ctx := c.Request.Context() - _ = ctx // TODO: use ctx. if c.GetHeader("Authorization") != fmt.Sprintf("Basic %s", cronSecret) { c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) @@ -55,29 +60,30 @@ func main() { var req request.CheckerRequest if err := c.ShouldBindJSON(&req); err != nil { + log.Ctx(ctx).Error().Err(err).Msg("failed to decode checker request") c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) return } - response, error := checker.Ping(httpClient, req) - if error != nil { + response, err := checker.Ping(ctx, httpClient, req) + if err != nil { // Add one more retry - response, error = checker.Ping(httpClient, req) - if error != nil { - checker.SendToTinyBird(checker.PingData{ + response, err = checker.Ping(ctx, httpClient, req) + if err != nil { + checker.SendToTinyBird(ctx, checker.PingData{ URL: req.URL, Region: flyRegion, - Message: error.Error(), + Message: err.Error(), CronTimestamp: req.CronTimestamp, Timestamp: req.CronTimestamp, MonitorID: req.MonitorID, WorkspaceID: req.WorkspaceID, }) if req.Status == "active" { - checker.UpdateStatus(checker.UpdateData{ + checker.UpdateStatus(ctx, checker.UpdateData{ MonitorId: req.MonitorID, Status: "error", - Message: error.Error(), + Message: err.Error(), Region: flyRegion, }) } @@ -89,10 +95,10 @@ func main() { if response.StatusCode < 200 || response.StatusCode >= 300 { // Add one more retry - response, error = checker.Ping(httpClient, req) + response, err = checker.Ping(ctx, httpClient, req) if response.StatusCode < 200 || response.StatusCode >= 300 && req.Status == "active" { - // If the status code is not within the 200 range, we update the status to error - checker.UpdateStatus(checker.UpdateData{ + // If the status code is not within the 200 range, we update the status to err + checker.UpdateStatus(ctx, checker.UpdateData{ MonitorId: req.MonitorID, Status: "error", StatusCode: response.StatusCode, @@ -104,7 +110,7 @@ func main() { // If the status was error and the status code is within the 200 range, we update the status to active if req.Status == "error" && response.StatusCode >= 200 && response.StatusCode < 300 { // If the status was error, we update it to active - checker.UpdateStatus(checker.UpdateData{ + checker.UpdateStatus(ctx, checker.UpdateData{ MonitorId: req.MonitorID, Status: "active", Region: flyRegion, @@ -112,7 +118,7 @@ func main() { }) } // We send the data to Tinybird - checker.SendToTinyBird(response) + checker.SendToTinyBird(ctx, response) c.JSON(http.StatusOK, gin.H{"message": "ok"}) return @@ -129,15 +135,15 @@ func main() { } go func() { - if err := httpServer.ListenAndServe(); err != nil { - // Add structured logs. + if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Ctx(ctx).Error().Err(err).Msg("failed to start http server") cancel() } }() <-ctx.Done() if err := httpServer.Shutdown(ctx); err != nil { - // Add structured logs. + log.Ctx(ctx).Error().Err(err).Msg("failed to shutdown http server") return } } diff --git a/apps/checker/go.mod b/apps/checker/go.mod index 12dcdf483..757997923 100644 --- a/apps/checker/go.mod +++ b/apps/checker/go.mod @@ -4,7 +4,7 @@ go 1.21.4 require ( github.com/gin-gonic/gin v1.9.1 - github.com/go-chi/chi/v5 v5.0.10 + github.com/rs/zerolog v1.31.0 ) require ( @@ -19,6 +19,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -28,7 +29,7 @@ require ( golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/sys v0.12.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/apps/checker/go.sum b/apps/checker/go.sum index ddb9e8077..bfd1a25b2 100644 --- a/apps/checker/go.sum +++ b/apps/checker/go.sum @@ -4,6 +4,7 @@ github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZX github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,8 +14,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= -github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -25,6 +24,7 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -36,6 +36,9 @@ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZX github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -45,8 +48,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -70,9 +77,10 @@ golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0 golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/apps/checker/ping.go b/apps/checker/ping.go index 850b6e230..44eeaad2a 100644 --- a/apps/checker/ping.go +++ b/apps/checker/ping.go @@ -2,6 +2,7 @@ package checker import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -11,6 +12,7 @@ import ( "time" "github.com/openstatushq/openstatus/apps/checker/request" + "github.com/rs/zerolog/log" ) type PingData struct { @@ -25,7 +27,7 @@ type PingData struct { Message string `json:"message,omitempty"` } -func SendToTinyBird(pingData PingData) { +func SendToTinyBird(ctx context.Context, pingData PingData) { url := "https://api.tinybird.co/v0/events?name=ping_response__v5" fmt.Printf("📈 Sending data to Tinybird for %+v \n", pingData) bearer := "Bearer " + os.Getenv("TINYBIRD_TOKEN") @@ -38,32 +40,33 @@ func SendToTinyBird(pingData PingData) { client := &http.Client{Timeout: time.Second * 10} _, err = client.Do(req) if err != nil { - fmt.Println(err) - panic(err) + log.Ctx(ctx).Error().Err(err).Msg("Error while sending data to Tinybird") } // Should we add a retry mechanism here? } -func Ping(client *http.Client, inputData request.CheckerRequest) (PingData, error) { +func Ping(ctx context.Context, client *http.Client, inputData request.CheckerRequest) (PingData, error) { + logger := log.Ctx(ctx).With().Str("monitor", inputData.URL).Logger() region := os.Getenv("FLY_REGION") - request, err := http.NewRequest(inputData.Method, inputData.URL, bytes.NewReader([]byte(inputData.Body))) + req, err := http.NewRequestWithContext(ctx, inputData.Method, inputData.URL, bytes.NewReader([]byte(inputData.Body))) if err != nil { - return PingData{}, fmt.Errorf("Unable to create request: %w", err) + logger.Error().Err(err).Msg("error while creating req") + return PingData{}, fmt.Errorf("unable to create req: %w", err) } - request.Header.Set("User-Agent", "OpenStatus/1.0") + req.Header.Set("User-Agent", "OpenStatus/1.0") // Setting headers for _, header := range inputData.Headers { if header.Key != "" && header.Value != "" { - request.Header.Set(header.Key, header.Value) + req.Header.Set(header.Key, header.Value) } } start := time.Now() - response, err := client.Do(request) + response, err := client.Do(req) latency := time.Since(start).Milliseconds() if err != nil { @@ -81,15 +84,16 @@ func Ping(client *http.Client, inputData request.CheckerRequest) (PingData, erro } } - return PingData{}, fmt.Errorf("Error with monitor %s: %w", inputData.URL, err) + logger.Error().Err(err).Msg("error while pinging") + return PingData{}, fmt.Errorf("error with monitor %s: %w", inputData.URL, err) } defer response.Body.Close() - _, err = io.ReadAll(response.Body) - - if err != nil { - return PingData{}, fmt.Errorf("Error while reading body from %s: %w", inputData.URL, err) + if _, err := io.ReadAll(response.Body); err != nil { + logger.Error().Err(err).Str("monitor", inputData.URL).Msg("error while reading body") + return PingData{}, fmt.Errorf("error while reading body from %s: %w", inputData.URL, err) } + return PingData{ Latency: latency, StatusCode: response.StatusCode, diff --git a/apps/checker/ping_test.go b/apps/checker/ping_test.go index ff31091ed..c0dfe57ab 100644 --- a/apps/checker/ping_test.go +++ b/apps/checker/ping_test.go @@ -1,6 +1,7 @@ package checker import ( + "context" "net/http" "testing" @@ -26,7 +27,7 @@ func Test_ping(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Ping(tt.args.client, tt.args.inputData) + got, err := Ping(context.Background(), tt.args.client, tt.args.inputData) if (err != nil) != tt.wantErr { t.Errorf("Ping() error = %v, wantErr %v", err, tt.wantErr) diff --git a/apps/checker/pkg/logger/logger.go b/apps/checker/pkg/logger/logger.go new file mode 100644 index 000000000..efbe032b8 --- /dev/null +++ b/apps/checker/pkg/logger/logger.go @@ -0,0 +1,19 @@ +package logger + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func Configure(logLevel string) { + level, err := zerolog.ParseLevel(logLevel) + if err != nil { + level = zerolog.InfoLevel + } + zerolog.SetGlobalLevel(level) + + zerolog.DefaultContextLogger = func() *zerolog.Logger { + logger := log.With().Caller().Logger() + return &logger + }() +} diff --git a/apps/checker/update.go b/apps/checker/update.go index 1828529c1..47b2c19cf 100644 --- a/apps/checker/update.go +++ b/apps/checker/update.go @@ -2,11 +2,13 @@ package checker import ( "bytes" + "context" "encoding/json" - "fmt" "net/http" "os" "time" + + "github.com/rs/zerolog/log" ) type UpdateData struct { @@ -17,20 +19,18 @@ type UpdateData struct { Region string `json:"region"` } -func UpdateStatus(updateData UpdateData) { +func UpdateStatus(ctx context.Context, updateData UpdateData) { url := "https://openstatus-api.fly.dev/updateStatus" - fmt.Println("URL:>", url) basic := "Basic " + os.Getenv("CRON_SECRET") payloadBuf := new(bytes.Buffer) json.NewEncoder(payloadBuf).Encode(updateData) - req, err := http.NewRequest("POST", url, payloadBuf) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payloadBuf) req.Header.Set("Authorization", basic) req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: time.Second * 10} - _, err = client.Do(req) - if err != nil { - panic(err) + if _, err = client.Do(req); err != nil { + log.Ctx(ctx).Error().Err(err).Msg("error while updating status") } // Should we add a retry mechanism here? }