diff --git a/changelogs/unreleased/mk-labkit-error-tracking.yml b/changelogs/unreleased/mk-labkit-error-tracking.yml new file mode 100644 index 0000000000000000000000000000000000000000..419cc9ae43d55489632f4c4c0c3c44b4f90fd654 --- /dev/null +++ b/changelogs/unreleased/mk-labkit-error-tracking.yml @@ -0,0 +1,5 @@ +--- +title: Migrate error tracking from raven to labkit +merge_request: 671 +author: +type: other diff --git a/go.mod b/go.mod index 6396e4194874395a44607ae321fab64924c574c1..20344f0081d34d28660f37f45fcb539421de4366 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,8 @@ require ( github.com/FZambia/sentinel v1.0.0 github.com/alecthomas/chroma v0.7.3 github.com/aws/aws-sdk-go v1.36.1 - github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/disintegration/imaging v1.6.2 - github.com/getsentry/raven-go v0.2.0 github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 github.com/golang/protobuf v1.4.3 github.com/gomodule/redigo v2.0.0+incompatible diff --git a/go.sum b/go.sum index 4796d40638b803da81384e62f38828a203b8337b..ddb08a1e846378457f609ab172c0933f999c1992 100644 --- a/go.sum +++ b/go.sum @@ -145,8 +145,6 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= diff --git a/internal/errortracker/sentry.go b/internal/errortracker/sentry.go new file mode 100644 index 0000000000000000000000000000000000000000..72a32c8d349fadae9894b73f86bb636030b3d88b --- /dev/null +++ b/internal/errortracker/sentry.go @@ -0,0 +1,60 @@ +package errortracker + +import ( + "fmt" + "net/http" + "os" + "runtime/debug" + + "gitlab.com/gitlab-org/labkit/errortracking" + + "gitlab.com/gitlab-org/labkit/log" +) + +// NewHandler allows us to handle panics in upstreams gracefully, by logging them +// using structured logging and reporting them into Sentry as `error`s with a +// proper correlation ID attached. +func NewHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + if p := recover(); p != nil { + fields := log.ContextFields(r.Context()) + log.WithFields(fields).Error(p) + debug.PrintStack() + // A panic isn't always an `error`, so we may have to convert it into one. + e, ok := p.(error) + if !ok { + e = fmt.Errorf("%v", p) + } + TrackFailedRequest(r, e, fields) + } + }() + + next.ServeHTTP(w, r) + }) +} + +func TrackFailedRequest(r *http.Request, err error, fields log.Fields) { + captureOpts := []errortracking.CaptureOption{ + errortracking.WithContext(r.Context()), + errortracking.WithRequest(r), + } + for k, v := range fields { + captureOpts = append(captureOpts, errortracking.WithField(k, fmt.Sprintf("%v", v))) + } + + errortracking.Capture(err, captureOpts...) +} + +func Initialize(version string) error { + // Use a custom environment variable (not SENTRY_DSN) to prevent + // clashes with gitlab-rails. + sentryDSN := os.Getenv("GITLAB_WORKHORSE_SENTRY_DSN") + sentryEnvironment := os.Getenv("GITLAB_WORKHORSE_SENTRY_ENVIRONMENT") + + return errortracking.Initialize( + errortracking.WithSentryDSN(sentryDSN), + errortracking.WithSentryEnvironment(sentryEnvironment), + errortracking.WithVersion(version), + ) +} diff --git a/internal/helper/helpers.go b/internal/helper/helpers.go index f9b46181579eb3f1fb9cc9690a7e4cb5d784500a..2e23f50b91387d773756cd50b4135bd8b2f5668f 100644 --- a/internal/helper/helpers.go +++ b/internal/helper/helpers.go @@ -14,50 +14,31 @@ import ( "syscall" "github.com/sebest/xff" - "gitlab.com/gitlab-org/labkit/log" - "gitlab.com/gitlab-org/labkit/mask" + + "gitlab.com/gitlab-org/gitlab-workhorse/internal/log" ) const NginxResponseBufferHeader = "X-Accel-Buffering" -func logErrorWithFields(r *http.Request, err error, fields log.Fields) { - if err != nil { - CaptureRavenError(r, err, fields) - } - - printError(r, err, fields) -} - -func CaptureAndFail(w http.ResponseWriter, r *http.Request, err error, msg string, code int) { +func CaptureAndFail(w http.ResponseWriter, r *http.Request, err error, msg string, code int, loggerCallbacks ...log.ConfigureLogger) { http.Error(w, msg, code) - logErrorWithFields(r, err, nil) -} + logger := log.WithRequest(r).WithError(err) + + for _, cb := range loggerCallbacks { + logger = cb(logger) + } -func Fail500(w http.ResponseWriter, r *http.Request, err error) { - CaptureAndFail(w, r, err, "Internal server error", http.StatusInternalServerError) + logger.Error(msg) } -func Fail500WithFields(w http.ResponseWriter, r *http.Request, err error, fields log.Fields) { - http.Error(w, "Internal server error", http.StatusInternalServerError) - logErrorWithFields(r, err, fields) +func Fail500(w http.ResponseWriter, r *http.Request, err error, loggerCallbacks ...log.ConfigureLogger) { + CaptureAndFail(w, r, err, "Internal server error", http.StatusInternalServerError, loggerCallbacks...) } func RequestEntityTooLarge(w http.ResponseWriter, r *http.Request, err error) { CaptureAndFail(w, r, err, "Request Entity Too Large", http.StatusRequestEntityTooLarge) } -func printError(r *http.Request, err error, fields log.Fields) { - if r != nil { - entry := log.WithContextFields(r.Context(), log.Fields{ - "method": r.Method, - "uri": mask.URL(r.RequestURI), - }) - entry.WithFields(fields).WithError(err).Error() - } else { - log.WithFields(fields).WithError(err).Error("unknown error") - } -} - func SetNoCacheHeaders(header http.Header) { header.Set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") header.Set("Pragma", "no-cache") @@ -97,7 +78,7 @@ func OpenFile(path string) (file *os.File, fi os.FileInfo, err error) { func URLMustParse(s string) *url.URL { u, err := url.Parse(s) if err != nil { - log.WithError(err).WithField("url", s).Fatal("urlMustParse") + log.WithError(err).WithFields(log.Fields{"url": s}).Error("urlMustParse") } return u } diff --git a/internal/helper/raven.go b/internal/helper/raven.go deleted file mode 100644 index 898e8ec85f80a4b41c3ce8c27eea106b8945c14c..0000000000000000000000000000000000000000 --- a/internal/helper/raven.go +++ /dev/null @@ -1,58 +0,0 @@ -package helper - -import ( - "net/http" - "reflect" - - raven "github.com/getsentry/raven-go" - - //lint:ignore SA1019 this was recently deprecated. Update workhorse to use labkit errortracking package. - correlation "gitlab.com/gitlab-org/labkit/correlation/raven" - - "gitlab.com/gitlab-org/labkit/log" -) - -var ravenHeaderBlacklist = []string{ - "Authorization", - "Private-Token", -} - -func CaptureRavenError(r *http.Request, err error, fields log.Fields) { - client := raven.DefaultClient - extra := raven.Extra{} - - for k, v := range fields { - extra[k] = v - } - - interfaces := []raven.Interface{} - if r != nil { - CleanHeadersForRaven(r) - interfaces = append(interfaces, raven.NewHttp(r)) - - //lint:ignore SA1019 this was recently deprecated. Update workhorse to use labkit errortracking package. - extra = correlation.SetExtra(r.Context(), extra) - } - - exception := &raven.Exception{ - Stacktrace: raven.NewStacktrace(2, 3, nil), - Value: err.Error(), - Type: reflect.TypeOf(err).String(), - } - interfaces = append(interfaces, exception) - - packet := raven.NewPacketWithExtra(err.Error(), extra, interfaces...) - client.Capture(packet, nil) -} - -func CleanHeadersForRaven(r *http.Request) { - if r == nil { - return - } - - for _, key := range ravenHeaderBlacklist { - if r.Header.Get(key) != "" { - r.Header.Set(key, "[redacted]") - } - } -} diff --git a/internal/imageresizer/image_resizer.go b/internal/imageresizer/image_resizer.go index 69e9496aec2c8699fab9c8be297751a8e61895d1..7d423b80067e00c0043fe847b7d44355aac9baa9 100644 --- a/internal/imageresizer/image_resizer.go +++ b/internal/imageresizer/image_resizer.go @@ -428,16 +428,18 @@ func logFields(startTime time.Time, params *resizeParams, outcome *resizeOutcome func handleOutcome(w http.ResponseWriter, req *http.Request, startTime time.Time, params *resizeParams, outcome *resizeOutcome) { fields := logFields(startTime, params, outcome) - log := log.WithRequest(req).WithFields(fields) + logger := log.WithRequest(req).WithFields(fields) switch outcome.status { case statusRequestFailure: if outcome.bytesWritten <= 0 { - helper.Fail500WithFields(w, req, outcome.err, fields) + helper.Fail500(w, req, outcome.err, func(b *log.Builder) *log.Builder { + return b.WithFields(fields) + }) } else { - log.WithError(outcome.err).Error(outcome.status) + logger.WithError(outcome.err).Error(outcome.status) } default: - log.Info(outcome.status) + logger.Info(outcome.status) } } diff --git a/internal/log/logging.go b/internal/log/logging.go index c65ec07743a61485835509ee0b7a7bc2f4a75dbb..9c19cde1395bc068696d78332851181ba1fb96a1 100644 --- a/internal/log/logging.go +++ b/internal/log/logging.go @@ -8,11 +8,13 @@ import ( "gitlab.com/gitlab-org/labkit/mask" "golang.org/x/net/context" - "gitlab.com/gitlab-org/gitlab-workhorse/internal/helper" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/errortracker" ) type Fields = log.Fields +type ConfigureLogger func(*Builder) *Builder + type Builder struct { entry *logrus.Entry fields log.Fields @@ -83,6 +85,6 @@ func (b *Builder) Error(args ...interface{}) { b.entry.Error(args...) if b.req != nil && b.err != nil { - helper.CaptureRavenError(b.req, b.err, b.fields) + errortracker.TrackFailedRequest(b.req, b.err, b.fields) } } diff --git a/internal/upstream/upstream.go b/internal/upstream/upstream.go index c81a21c0ecd9dd49e6de0018d80bee328d55903b..fd655a0767938c96feb49230a6b2aa5af6bbdf54 100644 --- a/internal/upstream/upstream.go +++ b/internal/upstream/upstream.go @@ -16,6 +16,7 @@ import ( "gitlab.com/gitlab-org/labkit/correlation" "gitlab.com/gitlab-org/gitlab-workhorse/internal/config" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/errortracker" "gitlab.com/gitlab-org/gitlab-workhorse/internal/helper" "gitlab.com/gitlab-org/gitlab-workhorse/internal/rejectmethods" "gitlab.com/gitlab-org/gitlab-workhorse/internal/upload" @@ -63,7 +64,7 @@ func NewUpstream(cfg config.Config, accessLogger *logrus.Logger) http.Handler { correlationOpts = append(correlationOpts, correlation.WithPropagation()) } - handler := correlation.InjectCorrelationID(&up, correlationOpts...) + handler := correlation.InjectCorrelationID(errortracker.NewHandler(&up), correlationOpts...) // TODO: move to LabKit https://gitlab.com/gitlab-org/gitlab-workhorse/-/issues/339 handler = rejectmethods.NewMiddleware(handler) return handler diff --git a/main.go b/main.go index 47ab63a875ac82c0d3b3786d005d207ecd71e689..c43ec45240f989a40c80a392d57612bceadc67fa 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "gitlab.com/gitlab-org/labkit/tracing" "gitlab.com/gitlab-org/gitlab-workhorse/internal/config" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/errortracker" "gitlab.com/gitlab-org/gitlab-workhorse/internal/queueing" "gitlab.com/gitlab-org/gitlab-workhorse/internal/redis" "gitlab.com/gitlab-org/gitlab-workhorse/internal/secret" @@ -156,6 +157,8 @@ func run(boot bootConfig, cfg config.Config) error { } defer closer.Close() + errortracker.Initialize(cfg.Version) + tracing.Initialize(tracing.WithServiceName("gitlab-workhorse")) log.WithField("version", Version).WithField("build_time", BuildTime).Print("Starting") @@ -223,7 +226,7 @@ func run(boot bootConfig, cfg config.Config) error { } defer accessCloser.Close() - up := wrapRaven(upstream.NewUpstream(cfg, accessLogger)) + up := upstream.NewUpstream(cfg, accessLogger) go func() { finalErrors <- http.Serve(listener, up) }() diff --git a/raven.go b/raven.go deleted file mode 100644 index f641203f142b60dd7fe51e948df1df4d5a3cbcd3..0000000000000000000000000000000000000000 --- a/raven.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "net/http" - "os" - - raven "github.com/getsentry/raven-go" - - "gitlab.com/gitlab-org/gitlab-workhorse/internal/helper" -) - -func wrapRaven(h http.Handler) http.Handler { - // Use a custom environment variable (not SENTRY_DSN) to prevent - // clashes with gitlab-rails. - sentryDSN := os.Getenv("GITLAB_WORKHORSE_SENTRY_DSN") - sentryEnvironment := os.Getenv("GITLAB_WORKHORSE_SENTRY_ENVIRONMENT") - raven.SetDSN(sentryDSN) // sentryDSN may be empty - - if sentryEnvironment != "" { - raven.SetEnvironment(sentryEnvironment) - } - - if sentryDSN == "" { - return h - } - - raven.DefaultClient.SetRelease(Version) - - return http.HandlerFunc(raven.RecoveryHandler( - func(w http.ResponseWriter, r *http.Request) { - defer func() { - if p := recover(); p != nil { - helper.CleanHeadersForRaven(r) - panic(p) - } - }() - - h.ServeHTTP(w, r) - })) -}