From 2df0bb179dd79da305836ce84dac7e96c932ee2c Mon Sep 17 00:00:00 2001 From: n0str Date: Fri, 29 Nov 2024 15:58:47 +0300 Subject: [PATCH] Add sentry processing --- pkg/server/errorshandler/handler_sentry.go | 72 ++++++++++++++++++++++ pkg/server/errorshandler/sentry_utils.go | 39 ++++++++++++ pkg/server/server.go | 7 +++ 3 files changed, 118 insertions(+) create mode 100644 pkg/server/errorshandler/handler_sentry.go create mode 100644 pkg/server/errorshandler/sentry_utils.go diff --git a/pkg/server/errorshandler/handler_sentry.go b/pkg/server/errorshandler/handler_sentry.go new file mode 100644 index 0000000..cf2d8ae --- /dev/null +++ b/pkg/server/errorshandler/handler_sentry.go @@ -0,0 +1,72 @@ +package errorshandler + +import ( + "fmt" + + "github.com/codex-team/hawk.collector/pkg/broker" + log "github.com/sirupsen/logrus" + "github.com/valyala/fasthttp" +) + +const SentryQueueName = "errors/sentry" +const CatcherType = "sentry" + +// HandleHTTP processes HTTP requests with JSON body +func (handler *Handler) HandleSentry(ctx *fasthttp.RequestCtx) { + if ctx.Request.Header.ContentLength() > handler.MaxErrorCatcherMessageSize { + log.Warnf("Incoming request with size %d", ctx.Request.Header.ContentLength()) + sendAnswerHTTP(ctx, ResponseMessage{Code: 400, Error: true, Message: "Request is too large"}) + return + } + + // check that X-Sentry-Auth header is available + auth := ctx.Request.Header.Peek("X-Sentry-Auth") + if auth == nil { + log.Warnf("Incoming request without X-Sentry-Auth header") + sendAnswerHTTP(ctx, ResponseMessage{Code: 400, Error: true, Message: "X-Sentry-Auth header is missing"}) + return + } + + hawkToken, err := getSentryKeyFromAuth(string(auth)) + if err != nil { + log.Warnf("Incoming request with invalid X-Sentry-Auth header: %s", err) + sendAnswerHTTP(ctx, ResponseMessage{Code: 400, Error: true, Message: err.Error()}) + return + } + + log.Debugf("Incoming request with hawk integration token: %s", hawkToken) + + body := ctx.PostBody() + + jsonBody, err := decompressGzipString(body) + if err != nil { + log.Warnf("Failed to decompress gzip body: %s", err) + sendAnswerHTTP(ctx, ResponseMessage{Code: 400, Error: true, Message: "Failed to decompress gzip body"}) + return + } + log.Debugf("Decompressed body: %s", jsonBody) + + projectId, ok := handler.AccountsMongoDBClient.ValidTokens[hawkToken] + if !ok { + log.Debugf("Token %s is not in the accounts cache", hawkToken) + sendAnswerHTTP(ctx, ResponseMessage{400, true, fmt.Sprintf("Integration token invalid: %s", hawkToken)}) + return + } + log.Debugf("Found project with ID %s for integration token %s", projectId, hawkToken) + + if handler.RedisClient.IsBlocked(projectId) { + handler.ErrorsBlockedByLimit.Inc() + sendAnswerHTTP(ctx, ResponseMessage{402, true, "Project has exceeded the events limit"}) + return + } + + // send serialized message to a broker + brokerMessage := broker.Message{Payload: jsonBody, Route: SentryQueueName} + log.Debugf("Send to queue: %s", brokerMessage) + handler.Broker.Chan <- brokerMessage + + // increment processed errors counter + handler.ErrorsProcessed.Inc() + + sendAnswerHTTP(ctx, ResponseMessage{200, false, "OK"}) +} diff --git a/pkg/server/errorshandler/sentry_utils.go b/pkg/server/errorshandler/sentry_utils.go new file mode 100644 index 0000000..c2c30e0 --- /dev/null +++ b/pkg/server/errorshandler/sentry_utils.go @@ -0,0 +1,39 @@ +package errorshandler + +import ( + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "strings" +) + +func decompressGzipString(gzipString []byte) ([]byte, error) { + reader, err := gzip.NewReader(bytes.NewReader(gzipString)) + if err != nil { + return []byte(""), fmt.Errorf("failed to create gzip reader: %w", err) + } + defer reader.Close() + + var result bytes.Buffer + _, err = io.Copy(&result, reader) + if err != nil { + return []byte(""), fmt.Errorf("failed to decompress data: %w", err) + } + + return result.Bytes(), nil +} + +func getSentryKeyFromAuth(auth string) (string, error) { + auth = strings.TrimPrefix(auth, "Sentry ") + pairs := strings.Split(auth, ", ") + for _, pair := range pairs { + kv := strings.SplitN(pair, "=", 2) + if len(kv) == 2 && kv[0] == "sentry_key" { + return kv[1], nil + } + } + + return "", errors.New("sentry_key not found") +} diff --git a/pkg/server/server.go b/pkg/server/server.go index b9a5e6f..7bca183 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -136,6 +136,13 @@ func (s *Server) handler(ctx *fasthttp.RequestCtx) { s.ErrorsHandler.HandleWebsocket(ctx) case "/release": s.ReleaseHandler.HandleHTTP(ctx) + case "/api/0/envelope/": + auth := ctx.Request.Header.Peek("X-Sentry-Auth") + if auth != nil { + s.ErrorsHandler.HandleSentry(ctx) + } else { + ctx.Error("X-Sentry-Auth not found", fasthttp.StatusBadRequest) + } default: ctx.Error("Not found", fasthttp.StatusNotFound) }