From 867e7c549f5e98e342148f8edf0099d1074166a0 Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 27 Sep 2024 10:15:08 +0200 Subject: [PATCH] feat: capture logged errors and forward them to sentry when enabled --- internal/admin/authz.go | 2 +- internal/admin/error.go | 2 +- internal/auth/middleware.go | 2 +- internal/command/server/dummy/run.go | 2 +- internal/lock/redis/locker.go | 2 +- internal/lock/redis/retry.go | 2 +- internal/proxy/director/director.go | 6 +++--- internal/proxy/director/layer/authn/layer.go | 14 +++++++------- .../director/layer/authn/oidc/authenticator.go | 4 ++-- .../proxy/director/layer/authn/oidc/client.go | 6 +++--- internal/proxy/director/layer/queue/queue.go | 18 +++++++++--------- .../proxy/director/layer/rewriter/layer.go | 4 ++-- internal/proxy/server.go | 8 ++++---- internal/setup/redis.go | 2 +- internal/setup/sentry.go | 4 ++++ internal/store/redis/helper.go | 4 ++-- 16 files changed, 43 insertions(+), 39 deletions(-) diff --git a/internal/admin/authz.go b/internal/admin/authz.go index 10b5f61..fda403a 100644 --- a/internal/admin/authz.go +++ b/internal/admin/authz.go @@ -70,7 +70,7 @@ func assertRequestUser(w http.ResponseWriter, r *http.Request) (auth.User, bool) ctx := r.Context() user, err := auth.CtxUser(ctx) if err != nil { - logger.Error(ctx, "could not retrieve user", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not retrieve user", logger.CapturedE(errors.WithStack(err))) forbidden(w, r) diff --git a/internal/admin/error.go b/internal/admin/error.go index 82fff75..2f4a159 100644 --- a/internal/admin/error.go +++ b/internal/admin/error.go @@ -35,5 +35,5 @@ func invalidDataErrorResponse(w http.ResponseWriter, r *http.Request, err *schem func logAndCaptureError(ctx context.Context, message string, err error) { sentry.CaptureException(err) - logger.Error(ctx, message, logger.E(err)) + logger.Error(ctx, message, logger.CapturedE(err)) } diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go index c62ebb5..5a40940 100644 --- a/internal/auth/middleware.go +++ b/internal/auth/middleware.go @@ -52,7 +52,7 @@ func Middleware(authenticators ...Authenticator) func(http.Handler) http.Handler for _, auth := range authenticators { user, err = auth.Authenticate(ctx, r) if err != nil { - logger.Debug(ctx, "could not authenticate request", logger.E(errors.WithStack(err))) + logger.Debug(ctx, "could not authenticate request", logger.CapturedE(errors.WithStack(err))) continue } diff --git a/internal/command/server/dummy/run.go b/internal/command/server/dummy/run.go index 1d8f9ea..55aa119 100644 --- a/internal/command/server/dummy/run.go +++ b/internal/command/server/dummy/run.go @@ -53,7 +53,7 @@ func RunCommand() *cli.Command { } if err := tmpl.Execute(w, data); err != nil { - logger.Error(ctx.Context, "could not execute template", logger.E(errors.WithStack(err))) + logger.Error(ctx.Context, "could not execute template", logger.CapturedE(errors.WithStack(err))) } }) diff --git a/internal/lock/redis/locker.go b/internal/lock/redis/locker.go index 337685c..7c50bb5 100644 --- a/internal/lock/redis/locker.go +++ b/internal/lock/redis/locker.go @@ -38,7 +38,7 @@ func (l *Locker) WithLock(ctx context.Context, key string, timeout time.Duration defer func() { if err := lock.Release(ctx); err != nil { - logger.Error(ctx, "could not release lock", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not release lock", logger.CapturedE(errors.WithStack(err))) } logger.Debug(ctx, "lock released") diff --git a/internal/lock/redis/retry.go b/internal/lock/redis/retry.go index 0b09329..25fb9a7 100644 --- a/internal/lock/redis/retry.go +++ b/internal/lock/redis/retry.go @@ -30,7 +30,7 @@ func retryWithBackoff(ctx context.Context, attempts int, fn func(ctx context.Con return errors.Wrapf(err, "execution failed after %d attempts", attempts) } - logger.Error(ctx, "error while executing func, retrying with backoff", logger.E(err), logger.F("backoffDelay", backoffDelay), logger.F("remainingAttempts", attempts-count)) + logger.Error(ctx, "error while executing func, retrying with backoff", logger.CapturedE(err), logger.F("backoffDelay", backoffDelay), logger.F("remainingAttempts", attempts-count)) time.Sleep(backoffDelay) diff --git a/internal/proxy/director/director.go b/internal/proxy/director/director.go index 9cd731d..38188ff 100644 --- a/internal/proxy/director/director.go +++ b/internal/proxy/director/director.go @@ -174,7 +174,7 @@ func (d *Director) RequestTransformer() proxy.RequestTransformer { return } - logger.Error(ctx, "could not retrieve layers from context", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not retrieve layers from context", logger.CapturedE(errors.WithStack(err))) return } @@ -226,7 +226,7 @@ func (d *Director) Middleware() proxy.Middleware { fn := func(w http.ResponseWriter, r *http.Request) { r, err := d.rewriteRequest(r) if err != nil { - logger.Error(r.Context(), "could not rewrite request", logger.E(errors.WithStack(err))) + logger.Error(r.Context(), "could not rewrite request", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -240,7 +240,7 @@ func (d *Director) Middleware() proxy.Middleware { return } - logger.Error(ctx, "could not retrieve proxy and layers from context", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not retrieve proxy and layers from context", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return diff --git a/internal/proxy/director/layer/authn/layer.go b/internal/proxy/director/layer/authn/layer.go index 0532ef1..cd5e635 100644 --- a/internal/proxy/director/layer/authn/layer.go +++ b/internal/proxy/director/layer/authn/layer.go @@ -29,7 +29,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware { options, err := fromStoreOptions(layer.Options) if err != nil { - logger.Error(ctx, "could not parse layer options", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not parse layer options", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -42,7 +42,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware { } err = errors.WithStack(err) - logger.Error(ctx, "could not execute pre-auth hook", logger.E(err)) + logger.Error(ctx, "could not execute pre-auth hook", logger.CapturedE(err)) l.renderErrorPage(w, r, layer, options, err) return @@ -68,7 +68,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware { } err = errors.WithStack(err) - logger.Error(ctx, "could not authenticate user", logger.E(err)) + logger.Error(ctx, "could not authenticate user", logger.CapturedE(err)) l.renderErrorPage(w, r, layer, options, err) return @@ -81,7 +81,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware { } err = errors.WithStack(err) - logger.Error(ctx, "could not apply rules", logger.E(err)) + logger.Error(ctx, "could not apply rules", logger.CapturedE(err)) l.renderErrorPage(w, r, layer, options, err) return @@ -99,7 +99,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware { } err = errors.WithStack(err) - logger.Error(ctx, "could not execute post-auth hook", logger.E(err)) + logger.Error(ctx, "could not execute post-auth hook", logger.CapturedE(err)) l.renderErrorPage(w, r, layer, options, err) return @@ -162,7 +162,7 @@ func (l *Layer) renderPage(w http.ResponseWriter, r *http.Request, page string, tmpl, err := template.New("").Funcs(sprig.FuncMap()).ParseGlob(pattern) if err != nil { - logger.Error(ctx, "could not load authn templates", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not load authn templates", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -171,7 +171,7 @@ func (l *Layer) renderPage(w http.ResponseWriter, r *http.Request, page string, w.Header().Add("Cache-Control", "no-cache") if err := tmpl.ExecuteTemplate(w, block, templateData); err != nil { - logger.Error(ctx, "could not render authn page", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not render authn page", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return diff --git a/internal/proxy/director/layer/authn/oidc/authenticator.go b/internal/proxy/director/layer/authn/oidc/authenticator.go index 09c566a..6693bee 100644 --- a/internal/proxy/director/layer/authn/oidc/authenticator.go +++ b/internal/proxy/director/layer/authn/oidc/authenticator.go @@ -44,7 +44,7 @@ func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request sess, err := a.store.Get(r, a.getCookieName(options.Cookie.Name, layer.Proxy, layer.Name)) if err != nil { - logger.Error(ctx, "could not retrieve session", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not retrieve session", logger.CapturedE(errors.WithStack(err))) } loginCallbackURL, err := a.getLoginCallbackURL(originalURL, layer.Proxy, layer.Name, options) @@ -128,7 +128,7 @@ func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, lay defer func() { if err := sess.Save(r, w); err != nil { - logger.Error(ctx, "could not save session", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not save session", logger.CapturedE(errors.WithStack(err))) } }() diff --git a/internal/proxy/director/layer/authn/oidc/client.go b/internal/proxy/director/layer/authn/oidc/client.go index fde7db6..11f9fac 100644 --- a/internal/proxy/director/layer/authn/oidc/client.go +++ b/internal/proxy/director/layer/authn/oidc/client.go @@ -47,7 +47,7 @@ func (c *Client) Provider() *oidc.Provider { func (c *Client) Authenticate(w http.ResponseWriter, r *http.Request, sess *sessions.Session, postLoginRedirectURL string) (*oidc.IDToken, error) { idToken, err := c.getIDToken(r, sess) if err != nil { - logger.Warn(r.Context(), "could not retrieve idtoken", logger.E(errors.WithStack(err))) + logger.Warn(r.Context(), "could not retrieve idtoken", logger.CapturedE(errors.WithStack(err))) c.login(w, r, sess, postLoginRedirectURL) @@ -68,7 +68,7 @@ func (c *Client) login(w http.ResponseWriter, r *http.Request, sess *sessions.Se sess.Values[sessionKeyPostLoginRedirectURL] = postLoginRedirectURL if err := sess.Save(r, w); err != nil { - logger.Error(ctx, "could not save session", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not save session", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -127,7 +127,7 @@ func (c *Client) HandleLogout(w http.ResponseWriter, r *http.Request, sess *sess rawIDToken, err := c.getRawIDToken(sess) if err != nil { - logger.Error(ctx, "could not retrieve raw id token", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not retrieve raw id token", logger.CapturedE(errors.WithStack(err))) } sess.Values[sessionKeyIDToken] = nil diff --git a/internal/proxy/director/layer/queue/queue.go b/internal/proxy/director/layer/queue/queue.go index e6068b5..c638578 100644 --- a/internal/proxy/director/layer/queue/queue.go +++ b/internal/proxy/director/layer/queue/queue.go @@ -52,7 +52,7 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware { options, err := fromStoreOptions(layer.Options, q.defaultKeepAlive) if err != nil { - logger.Error(ctx, "could not parse layer options", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not parse layer options", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -71,7 +71,7 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware { cookie, err := r.Cookie(cookieName) if err != nil && !errors.Is(err, http.ErrNoCookie) { - logger.Error(ctx, "could not retrieve cookie", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not retrieve cookie", logger.CapturedE(errors.WithStack(err))) } if cookie == nil { @@ -89,7 +89,7 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware { rank, err := q.adapter.Touch(ctx, queueName, sessionID) if err != nil { - logger.Error(ctx, "could not retrieve session rank", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not retrieve session rank", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -126,7 +126,7 @@ func (q *Queue) updateSessionsMetric(ctx context.Context, proxyName store.ProxyN status, err := q.adapter.Status(ctx, queueName) if err != nil { - logger.Error(ctx, "could not retrieve queue status", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not retrieve queue status", logger.CapturedE(errors.WithStack(err))) return } @@ -144,7 +144,7 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam status, err := q.adapter.Status(ctx, queueName) if err != nil { - logger.Error(ctx, "could not retrieve queue status", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not retrieve queue status", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -157,7 +157,7 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam tmpl, err := template.New("").Funcs(sprig.FuncMap()).ParseGlob(pattern) if err != nil { - logger.Error(ctx, "could not load queue templates", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not load queue templates", logger.CapturedE(errors.WithStack(err))) return } @@ -166,7 +166,7 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam }) if q.tmpl == nil { - logger.Error(ctx, "queue page templates not loaded", logger.E(errors.WithStack(err))) + logger.Error(ctx, "queue page templates not loaded", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -195,7 +195,7 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam w.WriteHeader(http.StatusServiceUnavailable) if err := q.tmpl.ExecuteTemplate(w, "queue", templateData); err != nil { - logger.Error(ctx, "could not render queue page", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not render queue page", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -211,7 +211,7 @@ func (q *Queue) refreshQueue(ctx context.Context, layerName store.LayerName, kee if err := q.adapter.Refresh(ctx, string(layerName), keepAlive); err != nil { logger.Error(ctx, "could not refresh queue", - logger.E(errors.WithStack(err)), + logger.CapturedE(errors.WithStack(err)), logger.F("queue", layerName), ) } diff --git a/internal/proxy/director/layer/rewriter/layer.go b/internal/proxy/director/layer/rewriter/layer.go index 7f44b8a..c05bf1b 100644 --- a/internal/proxy/director/layer/rewriter/layer.go +++ b/internal/proxy/director/layer/rewriter/layer.go @@ -32,7 +32,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware { options, err := fromStoreOptions(layer.Options) if err != nil { - logger.Error(ctx, "could not parse layer options", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not parse layer options", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -52,7 +52,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware { return } - logger.Error(ctx, "could not apply request rules", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not apply request rules", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return diff --git a/internal/proxy/server.go b/internal/proxy/server.go index dcdf0ef..d9f095e 100644 --- a/internal/proxy/server.go +++ b/internal/proxy/server.go @@ -223,7 +223,7 @@ func (s *Server) createReverseProxy(ctx context.Context, target *url.URL) *httpu func (s *Server) handleDefault(w http.ResponseWriter, r *http.Request) { err := errors.Errorf("no proxy target found") - logger.Error(r.Context(), "proxy error", logger.E(err)) + logger.Error(r.Context(), "proxy error", logger.CapturedE(err)) sentry.CaptureException(err) s.renderErrorPage(w, r, err, http.StatusBadGateway, http.StatusText(http.StatusBadGateway)) @@ -232,7 +232,7 @@ func (s *Server) handleDefault(w http.ResponseWriter, r *http.Request) { func (s *Server) handleError(w http.ResponseWriter, r *http.Request, err error) { err = errors.WithStack(err) - logger.Error(r.Context(), "proxy error", logger.E(err)) + logger.Error(r.Context(), "proxy error", logger.CapturedE(err)) sentry.CaptureException(err) s.renderErrorPage(w, r, err, http.StatusBadGateway, http.StatusText(http.StatusBadGateway)) @@ -266,7 +266,7 @@ func (s *Server) renderPage(w http.ResponseWriter, r *http.Request, page string, tmpl, err := template.New("").Funcs(sprig.FuncMap()).ParseGlob(pattern) if err != nil { - logger.Error(ctx, "could not load proxy templates", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not load proxy templates", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -287,7 +287,7 @@ func (s *Server) renderPage(w http.ResponseWriter, r *http.Request, page string, } if err := blockTmpl.Execute(w, templateData); err != nil { - logger.Error(ctx, "could not render proxy page", logger.E(errors.WithStack(err))) + logger.Error(ctx, "could not render proxy page", logger.CapturedE(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return diff --git a/internal/setup/redis.go b/internal/setup/redis.go index e171fcc..1800e39 100644 --- a/internal/setup/redis.go +++ b/internal/setup/redis.go @@ -56,7 +56,7 @@ func newRedisClient(conf config.RedisConfig) redis.UniversalClient { for range timer.C { if _, err := client.Ping(ctx).Result(); err != nil { - logger.Error(ctx, "redis disconnected", logger.E(errors.WithStack(err))) + logger.Error(ctx, "redis disconnected", logger.CapturedE(errors.WithStack(err))) connected = false continue } diff --git a/internal/setup/sentry.go b/internal/setup/sentry.go index 0bb742b..e980e66 100644 --- a/internal/setup/sentry.go +++ b/internal/setup/sentry.go @@ -34,6 +34,10 @@ func SetupSentry(ctx context.Context, conf config.SentryConfig, release string) return nil, errors.WithStack(err) } + logger.SetCaptureFunc(func(err error) { + sentry.CaptureException(err) + }) + flush := func() { sentry.Flush(time.Duration(*conf.FlushTimeout)) } diff --git a/internal/store/redis/helper.go b/internal/store/redis/helper.go index 954b171..2aca011 100644 --- a/internal/store/redis/helper.go +++ b/internal/store/redis/helper.go @@ -81,7 +81,7 @@ func WithRetry(ctx context.Context, client redis.UniversalClient, key string, fn for attempt := 0; attempt < maxAttempts; attempt++ { if err = WithTx(ctx, client, key, fn); err != nil { err = errors.WithStack(err) - logger.Debug(ctx, "redis transaction failed", logger.E(err)) + logger.Debug(ctx, "redis transaction failed", logger.CapturedE(err)) if errors.Is(err, redis.TxFailedErr) { logger.Debug(ctx, "retrying redis transaction", logger.F("attempts", attempt), logger.F("delay", delay)) @@ -97,7 +97,7 @@ func WithRetry(ctx context.Context, client redis.UniversalClient, key string, fn return nil } - logger.Error(ctx, "redis error", logger.E(errors.WithStack(err))) + logger.Error(ctx, "redis error", logger.CapturedE(errors.WithStack(err))) return errors.WithStack(redis.TxFailedErr) }