feat: capture logged errors and forward them to sentry when enabled
Cadoles/bouncer/pipeline/head This commit looks good Details

This commit is contained in:
wpetit 2024-09-27 10:15:08 +02:00
parent 169578c25d
commit 867e7c549f
16 changed files with 43 additions and 39 deletions

View File

@ -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)

View File

@ -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))
}

View File

@ -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
}

View File

@ -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)))
}
})

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)))
}
}()

View File

@ -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

View File

@ -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),
)
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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))
}

View File

@ -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)
}