Openstatus www.openstatus.dev
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(checker): extract tinybird in its own package (#569)

authored by

Arthur EICHELBERGER and committed by
GitHub
440079a2 b6979d45

+164 -31
+14 -9
apps/checker/cmd/main.go
··· 13 13 "github.com/gin-gonic/gin" 14 14 "github.com/openstatushq/openstatus/apps/checker" 15 15 "github.com/openstatushq/openstatus/apps/checker/pkg/logger" 16 + "github.com/openstatushq/openstatus/apps/checker/pkg/tinybird" 16 17 "github.com/openstatushq/openstatus/apps/checker/request" 17 18 "github.com/rs/zerolog/log" 18 19 ) ··· 32 33 // environment variables. 33 34 flyRegion := env("FLY_REGION", "local") 34 35 cronSecret := env("CRON_SECRET", "") 36 + tinyBirdToken := env("TINYBIRD_TOKEN", "") 35 37 logLevel := env("LOG_LEVEL", "warn") 36 38 37 39 logger.Configure(logLevel) 38 40 41 + // packages. 39 42 httpClient := &http.Client{} 40 43 defer httpClient.CloseIdleConnections() 41 44 45 + tinybirdClient := tinybird.NewClient(httpClient, tinyBirdToken) 46 + 42 47 router := gin.New() 43 48 router.POST("/checker", func(c *gin.Context) { 44 49 ctx := c.Request.Context() ··· 64 69 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) 65 70 return 66 71 } 67 - fmt.Printf("🚀 Start processing for %+v \n", req) 68 72 69 73 response, err := checker.Ping(ctx, httpClient, req) 70 74 if err != nil { 71 - fmt.Printf("1️⃣ first retry for %+v \n", req) 72 - // Add one more retry 73 75 response, err = checker.Ping(ctx, httpClient, req) 74 76 if err != nil { 75 - fmt.Printf("2️⃣ second retry for %+v \n", req) 76 - 77 - checker.SendToTinyBird(ctx, checker.PingData{ 77 + if err := tinybirdClient.SendEvent(ctx, checker.PingData{ 78 78 URL: req.URL, 79 79 Region: flyRegion, 80 80 Message: err.Error(), ··· 82 82 Timestamp: req.CronTimestamp, 83 83 MonitorID: req.MonitorID, 84 84 WorkspaceID: req.WorkspaceID, 85 - }) 85 + }); err != nil { 86 + log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 87 + } 88 + 86 89 if req.Status == "active" { 87 90 checker.UpdateStatus(ctx, checker.UpdateData{ 88 91 MonitorId: req.MonitorID, ··· 121 124 StatusCode: response.StatusCode, 122 125 }) 123 126 } 124 - // We send the data to Tinybird 125 - checker.SendToTinyBird(ctx, response) 127 + 128 + if err := tinybirdClient.SendEvent(ctx, response); err != nil { 129 + log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 130 + } 126 131 127 132 c.JSON(http.StatusOK, gin.H{"message": "ok"}) 128 133 return
+3
apps/checker/go.mod
··· 5 5 require ( 6 6 github.com/gin-gonic/gin v1.9.1 7 7 github.com/rs/zerolog v1.31.0 8 + github.com/stretchr/testify v1.8.3 8 9 ) 9 10 10 11 require ( 11 12 github.com/bytedance/sonic v1.9.1 // indirect 12 13 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 14 + github.com/davecgh/go-spew v1.1.1 // indirect 13 15 github.com/gabriel-vasile/mimetype v1.4.2 // indirect 14 16 github.com/gin-contrib/sse v0.1.0 // indirect 15 17 github.com/go-playground/locales v0.14.1 // indirect ··· 24 26 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 25 27 github.com/modern-go/reflect2 v1.0.2 // indirect 26 28 github.com/pelletier/go-toml/v2 v2.0.8 // indirect 29 + github.com/pmezard/go-difflib v1.0.0 // indirect 27 30 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 28 31 github.com/ugorji/go/codec v1.2.11 // indirect 29 32 golang.org/x/arch v0.3.0 // indirect
-2
apps/checker/go.sum
··· 39 39 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 40 40 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 41 41 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 42 - github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 43 42 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 44 43 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 45 44 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= ··· 81 80 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 82 81 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 83 82 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 84 - golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 85 83 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 86 84 golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 87 85 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-20
apps/checker/ping.go
··· 3 3 import ( 4 4 "bytes" 5 5 "context" 6 - "encoding/json" 7 6 "fmt" 8 7 "io" 9 8 "net/http" ··· 25 24 URL string `json:"url"` 26 25 Region string `json:"region"` 27 26 Message string `json:"message,omitempty"` 28 - } 29 - 30 - func SendToTinyBird(ctx context.Context, pingData PingData) { 31 - url := "https://api.tinybird.co/v0/events?name=ping_response__v5" 32 - fmt.Printf("📈 Sending data to Tinybird for %+v \n", pingData) 33 - bearer := "Bearer " + os.Getenv("TINYBIRD_TOKEN") 34 - payloadBuf := new(bytes.Buffer) 35 - json.NewEncoder(payloadBuf).Encode(pingData) 36 - req, err := http.NewRequest("POST", url, payloadBuf) 37 - req.Header.Set("Authorization", bearer) 38 - req.Header.Set("Content-Type", "application/json") 39 - 40 - client := &http.Client{Timeout: time.Second * 10} 41 - _, err = client.Do(req) 42 - if err != nil { 43 - log.Ctx(ctx).Error().Err(err).Msg("Error while sending data to Tinybird") 44 - } 45 - // Should we add a retry mechanism here? 46 - 47 27 } 48 28 49 29 func Ping(ctx context.Context, client *http.Client, inputData request.CheckerRequest) (PingData, error) {
+69
apps/checker/pkg/tinybird/client.go
··· 1 + package tinybird 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "encoding/json" 7 + "fmt" 8 + "net/http" 9 + "net/url" 10 + 11 + "github.com/rs/zerolog/log" 12 + ) 13 + 14 + const baseURL = "https://api.tinybird.co/v0/events" 15 + 16 + type Client interface { 17 + SendEvent(ctx context.Context, event any) error 18 + } 19 + 20 + type client struct { 21 + httpClient *http.Client 22 + apiKey string 23 + } 24 + 25 + func NewClient(httpClient *http.Client, apiKey string) Client { 26 + return client{ 27 + httpClient: httpClient, 28 + apiKey: apiKey, 29 + } 30 + } 31 + 32 + func (c client) SendEvent(ctx context.Context, event any) error { 33 + requestURL, err := url.Parse(baseURL) 34 + if err != nil { 35 + log.Ctx(ctx).Error().Err(err).Msg("unable to parse url") 36 + return fmt.Errorf("unable to parse url: %w", err) 37 + } 38 + 39 + q := requestURL.Query() 40 + q.Add("name", "ping_response__v5") 41 + requestURL.RawQuery = q.Encode() 42 + 43 + var payload bytes.Buffer 44 + if err := json.NewEncoder(&payload).Encode(event); err != nil { 45 + log.Ctx(ctx).Error().Err(err).Msg("unable to encode payload") 46 + return fmt.Errorf("unable to encode payload: %w", err) 47 + } 48 + 49 + req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL.String(), bytes.NewReader(payload.Bytes())) 50 + if err != nil { 51 + log.Ctx(ctx).Error().Err(err).Msg("unable to create request") 52 + return fmt.Errorf("unable to create request: %w", err) 53 + } 54 + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) 55 + 56 + resp, err := c.httpClient.Do(req) 57 + if err != nil { 58 + log.Ctx(ctx).Error().Err(err).Msg("unable to send request") 59 + return fmt.Errorf("unable to send request: %w", err) 60 + } 61 + defer resp.Body.Close() 62 + 63 + if resp.StatusCode != http.StatusOK { 64 + log.Ctx(ctx).Error().Str("status", resp.Status).Msg("unexpected status code") 65 + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) 66 + } 67 + 68 + return nil 69 + }
+78
apps/checker/pkg/tinybird/client_test.go
··· 1 + package tinybird_test 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "net/http" 7 + "testing" 8 + 9 + "github.com/openstatushq/openstatus/apps/checker/pkg/tinybird" 10 + "github.com/stretchr/testify/require" 11 + ) 12 + 13 + type interceptorHTTPClient struct { 14 + f func(req *http.Request) (*http.Response, error) 15 + } 16 + 17 + func (i *interceptorHTTPClient) RoundTrip(req *http.Request) (*http.Response, error) { 18 + return i.f(req) 19 + } 20 + 21 + func (i *interceptorHTTPClient) GetHTTPClient() *http.Client { 22 + return &http.Client{ 23 + Transport: i, 24 + } 25 + } 26 + 27 + func TestSendEvent(t *testing.T) { 28 + t.Parallel() 29 + 30 + ctx, cancel := context.WithCancel(context.Background()) 31 + defer cancel() 32 + 33 + t.Run("it should return an error if it can not send the event", func(t *testing.T) { 34 + interceptor := &interceptorHTTPClient{ 35 + f: func(req *http.Request) (*http.Response, error) { 36 + return nil, fmt.Errorf("unable to send request") 37 + }, 38 + } 39 + 40 + client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 41 + 42 + err := client.SendEvent(ctx, "event") 43 + require.Error(t, err) 44 + }) 45 + 46 + t.Run("it should return an error if the response status code is not 200", func(t *testing.T) { 47 + interceptor := &interceptorHTTPClient{ 48 + f: func(req *http.Request) (*http.Response, error) { 49 + return &http.Response{ 50 + StatusCode: http.StatusInternalServerError, 51 + }, nil 52 + }, 53 + } 54 + 55 + client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 56 + 57 + err := client.SendEvent(ctx, "event") 58 + require.Error(t, err) 59 + }) 60 + 61 + t.Run("it should succeed and return nothing", func(t *testing.T) { 62 + var url string 63 + interceptor := &interceptorHTTPClient{ 64 + f: func(req *http.Request) (*http.Response, error) { 65 + url = req.URL.String() 66 + return &http.Response{ 67 + StatusCode: http.StatusOK, 68 + }, nil 69 + }, 70 + } 71 + 72 + client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 73 + 74 + err := client.SendEvent(ctx, "event") 75 + require.NoError(t, err) 76 + require.Equal(t, "https://api.tinybird.co/v0/events?name=ping_response__v5", url) 77 + }) 78 + }