feat: sentry integration
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
ref #3
This commit is contained in:
@ -1,11 +1,14 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
const ErrCodeAlreadyExist api.ErrorCode = "already-exist"
|
||||
@ -29,3 +32,8 @@ func invalidDataErrorResponse(w http.ResponseWriter, r *http.Request, err *schem
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func logAndCaptureError(ctx context.Context, message string, err error) {
|
||||
sentry.CaptureException(err)
|
||||
logger.Error(ctx, message, logger.E(err))
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
@ -10,7 +11,6 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type QueryLayerResponse struct {
|
||||
@ -38,7 +38,7 @@ func (s *Server) queryLayer(w http.ResponseWriter, r *http.Request) {
|
||||
options...,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not list layers", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not list layers", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -85,7 +85,7 @@ func (s *Server) getLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not get layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not get layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -120,7 +120,7 @@ func (s *Server) deleteLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not delete layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not delete layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -156,7 +156,7 @@ func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
layerName, err := store.ValidateName(createLayerReq.Name)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "invalid 'name' parameter", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "invalid 'name' parameter", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil)
|
||||
|
||||
return
|
||||
@ -165,7 +165,7 @@ func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) {
|
||||
layerType := store.LayerType(createLayerReq.Type)
|
||||
|
||||
if !setup.LayerTypeExists(layerType) {
|
||||
logger.Error(r.Context(), "unknown layer type", logger.E(errors.WithStack(err)), logger.F("layerType", layerType))
|
||||
logAndCaptureError(ctx, fmt.Sprintf("unknown layer type '%s'", layerType), errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil)
|
||||
|
||||
return
|
||||
@ -179,7 +179,7 @@ func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not create layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not create layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -223,7 +223,7 @@ func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not get layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not get layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -247,7 +247,7 @@ func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
||||
if updateLayerReq.Options != nil {
|
||||
layerOptionsSchema, err := setup.GetLayerOptionsSchema(layer.Type)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not retrieve layer options schema", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not retrieve layer options schema", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -258,7 +258,7 @@ func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
||||
}(updateLayerReq.Options)
|
||||
|
||||
if err := schema.Validate(ctx, layerOptionsSchema, rawOptions); err != nil {
|
||||
logger.Error(r.Context(), "could not validate layer options", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not validate layer options", errors.WithStack(err))
|
||||
|
||||
var invalidDataErr *schema.InvalidDataError
|
||||
if errors.As(err, &invalidDataErr) {
|
||||
@ -286,7 +286,7 @@ func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not update layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not update layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -300,21 +300,7 @@ func getLayerName(w http.ResponseWriter, r *http.Request) (store.LayerName, bool
|
||||
|
||||
name, err := store.ValidateName(rawLayerName)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse layer name", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
return store.LayerName(name), true
|
||||
}
|
||||
|
||||
func geLayerName(w http.ResponseWriter, r *http.Request) (store.LayerName, bool) {
|
||||
rawLayerName := chi.URLParam(r, "layerName")
|
||||
|
||||
name, err := store.ValidateName(rawLayerName)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse layer name", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(r.Context(), "could not parse layer name", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return "", false
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type QueryProxyResponse struct {
|
||||
@ -37,7 +36,7 @@ func (s *Server) queryProxy(w http.ResponseWriter, r *http.Request) {
|
||||
options...,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not list proxies", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not list proxies", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -79,7 +78,7 @@ func (s *Server) getProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not get proxy", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not get proxy", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -109,7 +108,7 @@ func (s *Server) deleteProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not delete proxy", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not delete proxy", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -140,14 +139,14 @@ func (s *Server) createProxy(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
name, err := store.ValidateName(createProxyReq.Name)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'name' parameter", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not parse 'name' parameter", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := url.Parse(createProxyReq.To); err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'to' parameter", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not parse 'to' parameter", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
@ -161,7 +160,7 @@ func (s *Server) createProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not create proxy", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not create proxy", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -207,7 +206,7 @@ func (s *Server) updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
if updateProxyReq.To != nil {
|
||||
_, err := url.Parse(*updateProxyReq.To)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'to' parameter", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not parse 'to' parameter", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
@ -235,7 +234,7 @@ func (s *Server) updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not update proxy", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not update proxy", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -249,7 +248,7 @@ func getProxyName(w http.ResponseWriter, r *http.Request) (store.ProxyName, bool
|
||||
|
||||
name, err := store.ValidateName(rawProxyName)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse proxy name", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(r.Context(), "could not parse proxy name", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return "", false
|
||||
@ -263,7 +262,7 @@ func getIntQueryParam(w http.ResponseWriter, r *http.Request, param string, defa
|
||||
if rawValue != "" {
|
||||
value, err := strconv.ParseInt(rawValue, 10, 64)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse int param", logger.F("param", param), logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(r.Context(), "could not parse int param", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return 0, false
|
||||
@ -296,7 +295,7 @@ func getStringableSliceValues[T ~string](w http.ResponseWriter, r *http.Request,
|
||||
for _, rv := range rawValues {
|
||||
v, err := validate(rv)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse ids slice param", logger.F("param", param), logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(r.Context(), "could not parse ids slice param", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return nil, false
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/jwk"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
@ -92,6 +93,16 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
||||
|
||||
router.Use(middleware.Logger)
|
||||
|
||||
if s.serverConfig.Sentry.DSN != "" {
|
||||
logger.Info(ctx, "enabling sentry http middleware")
|
||||
|
||||
sentryMiddleware := sentryhttp.New(sentryhttp.Options{
|
||||
Repanic: true,
|
||||
})
|
||||
|
||||
router.Use(sentryMiddleware.Handle)
|
||||
}
|
||||
|
||||
corsMiddleware := cors.New(cors.Options{
|
||||
AllowedOrigins: s.serverConfig.CORS.AllowedOrigins,
|
||||
AllowedMethods: s.serverConfig.CORS.AllowedMethods,
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@ -90,6 +91,8 @@ func Main(buildDate, projectVersion, gitRef, defaultConfigPath string, commands
|
||||
return
|
||||
}
|
||||
|
||||
sentry.CaptureException(err)
|
||||
|
||||
debug := ctx.Bool("debug")
|
||||
|
||||
if !debug {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
@ -27,6 +28,14 @@ func RunCommand() *cli.Command {
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
projectVersion := ctx.String("projectVersion")
|
||||
flushSentry, err := setup.SetupSentry(ctx.Context, conf.Admin.Sentry, projectVersion)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize sentry client")
|
||||
}
|
||||
|
||||
defer flushSentry()
|
||||
|
||||
srv := admin.NewServer(
|
||||
admin.WithServerConfig(conf.Admin),
|
||||
admin.WithRedisConfig(conf.Redis),
|
||||
|
@ -28,6 +28,14 @@ func RunCommand() *cli.Command {
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
projectVersion := ctx.String("projectVersion")
|
||||
flushSentry, err := setup.SetupSentry(ctx.Context, conf.Proxy.Sentry, projectVersion)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize sentry client")
|
||||
}
|
||||
|
||||
defer flushSentry()
|
||||
|
||||
layers, err := setup.GetLayers(ctx.Context, conf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize director layers")
|
||||
|
@ -5,6 +5,7 @@ type AdminServerConfig struct {
|
||||
CORS CORSConfig `yaml:"cors"`
|
||||
Auth AuthConfig `yaml:"auth"`
|
||||
Metrics MetricsConfig `yaml:"metrics"`
|
||||
Sentry SentryConfig `yaml:"sentry"`
|
||||
}
|
||||
|
||||
func NewDefaultAdminServerConfig() AdminServerConfig {
|
||||
@ -13,6 +14,7 @@ func NewDefaultAdminServerConfig() AdminServerConfig {
|
||||
CORS: NewDefaultCORSConfig(),
|
||||
Auth: NewDefaultAuthConfig(),
|
||||
Metrics: NewDefaultMetricsConfig(),
|
||||
Sentry: NewDefaultSentryConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,29 @@ func (ii *InterpolatedInt) UnmarshalYAML(value *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type InterpolatedFloat float64
|
||||
|
||||
func (ifl *InterpolatedFloat) UnmarshalYAML(value *yaml.Node) error {
|
||||
var str string
|
||||
|
||||
if err := value.Decode(&str); err != nil {
|
||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
||||
}
|
||||
|
||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
||||
str = os.Getenv(match[1])
|
||||
}
|
||||
|
||||
floatVal, err := strconv.ParseFloat(str, 10)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not parse float '%v', line '%d'", str, value.Line)
|
||||
}
|
||||
|
||||
*ifl = InterpolatedFloat(floatVal)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type InterpolatedBool bool
|
||||
|
||||
func (ib *InterpolatedBool) UnmarshalYAML(value *yaml.Node) error {
|
||||
|
@ -7,6 +7,7 @@ type ProxyServerConfig struct {
|
||||
Metrics MetricsConfig `yaml:"metrics"`
|
||||
Transport TransportConfig `yaml:"transport"`
|
||||
Dial DialConfig `yaml:"dial"`
|
||||
Sentry SentryConfig `yaml:"sentry"`
|
||||
}
|
||||
|
||||
// See https://pkg.go.dev/net/http#Transport
|
||||
@ -32,6 +33,7 @@ func NewDefaultProxyServerConfig() ProxyServerConfig {
|
||||
Metrics: NewDefaultMetricsConfig(),
|
||||
Transport: NewDefaultTransportConfig(),
|
||||
Dial: NewDefaultDialConfig(),
|
||||
Sentry: NewDefaultSentryConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
|
43
internal/config/sentry.go
Normal file
43
internal/config/sentry.go
Normal file
@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
// Sentry configuration
|
||||
// See https://pkg.go.dev/github.com/getsentry/sentry-go?utm_source=godoc#ClientOptions
|
||||
type SentryConfig struct {
|
||||
DSN InterpolatedString `yaml:"dsn"`
|
||||
Debug InterpolatedBool `yaml:"debug"`
|
||||
FlushTimeout *InterpolatedDuration `yaml:"flushTimeout"`
|
||||
AttachStacktrace InterpolatedBool `yaml:"attachStacktrace"`
|
||||
SampleRate InterpolatedFloat `yaml:"sampleRate"`
|
||||
EnableTracing InterpolatedBool `yaml:"enableTracing"`
|
||||
TracesSampleRate InterpolatedFloat `yaml:"tracesSampleRate"`
|
||||
ProfilesSampleRate InterpolatedFloat `yaml:"profilesSampleRate"`
|
||||
IgnoreErrors InterpolatedStringSlice `yaml:"ignoreErrors"`
|
||||
SendDefaultPII InterpolatedBool `yaml:"sendDefaultPII"`
|
||||
ServerName InterpolatedString `yaml:"serverName"`
|
||||
Environment InterpolatedString `yaml:"environment"`
|
||||
MaxBreadcrumbs InterpolatedInt `yaml:"maxBreadcrumbs"`
|
||||
MaxSpans InterpolatedInt `yaml:"maxSpans"`
|
||||
MaxErrorDepth InterpolatedInt `yaml:"maxErrorDepth"`
|
||||
}
|
||||
|
||||
func NewDefaultSentryConfig() SentryConfig {
|
||||
return SentryConfig{
|
||||
DSN: "",
|
||||
Debug: false,
|
||||
FlushTimeout: NewInterpolatedDuration(2 * time.Second),
|
||||
AttachStacktrace: true,
|
||||
SampleRate: 1,
|
||||
EnableTracing: true,
|
||||
TracesSampleRate: 0.2,
|
||||
ProfilesSampleRate: 1,
|
||||
IgnoreErrors: []string{},
|
||||
SendDefaultPII: false,
|
||||
ServerName: "",
|
||||
Environment: "",
|
||||
MaxBreadcrumbs: 0,
|
||||
MaxSpans: 1000,
|
||||
MaxErrorDepth: 10,
|
||||
}
|
||||
}
|
43
internal/logger/writer.go
Normal file
43
internal/logger/writer.go
Normal file
@ -0,0 +1,43 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
ctx context.Context
|
||||
level logger.Level
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
w.log(string(p))
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *Writer) log(message string) {
|
||||
switch w.level {
|
||||
case logger.LevelDebug:
|
||||
logger.Debug(w.ctx, message)
|
||||
case logger.LevelInfo:
|
||||
logger.Info(w.ctx, message)
|
||||
case logger.LevelWarn:
|
||||
logger.Warn(w.ctx, message)
|
||||
case logger.LevelError:
|
||||
logger.Error(w.ctx, message)
|
||||
case logger.LevelCritical:
|
||||
logger.Critical(w.ctx, message)
|
||||
default:
|
||||
logger.Debug(w.ctx, message)
|
||||
}
|
||||
}
|
||||
|
||||
func NewWriter(ctx context.Context, level logger.Level) *Writer {
|
||||
return &Writer{ctx, level}
|
||||
}
|
||||
|
||||
var _ io.Writer = &Writer{}
|
@ -15,6 +15,8 @@ import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/getsentry/sentry-go"
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/pkg/errors"
|
||||
@ -89,6 +91,16 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
||||
|
||||
router.Use(middleware.RequestLogger(bouncerChi.NewLogFormatter()))
|
||||
|
||||
if s.serverConfig.Sentry.DSN != "" {
|
||||
logger.Info(ctx, "enabling sentry http middleware")
|
||||
|
||||
sentryMiddleware := sentryhttp.New(sentryhttp.Options{
|
||||
Repanic: true,
|
||||
})
|
||||
|
||||
router.Use(sentryMiddleware.Handle)
|
||||
}
|
||||
|
||||
if s.serverConfig.Metrics.Enabled {
|
||||
metrics := s.serverConfig.Metrics
|
||||
|
||||
@ -169,7 +181,10 @@ func (s *Server) createReverseProxy(ctx context.Context, target *url.URL) *httpu
|
||||
}
|
||||
|
||||
func (s *Server) errorHandler(w http.ResponseWriter, r *http.Request, err error) {
|
||||
logger.Error(r.Context(), "proxy error", logger.E(errors.WithStack(err)))
|
||||
err = errors.WithStack(err)
|
||||
|
||||
logger.Error(r.Context(), "proxy error", logger.E(err))
|
||||
sentry.CaptureException(err)
|
||||
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
|
42
internal/setup/sentry.go
Normal file
42
internal/setup/sentry.go
Normal file
@ -0,0 +1,42 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
loggerWriter "forge.cadoles.com/cadoles/bouncer/internal/logger"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func SetupSentry(ctx context.Context, conf config.SentryConfig, release string) (func(), error) {
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: string(conf.DSN),
|
||||
Debug: bool(conf.Debug),
|
||||
AttachStacktrace: bool(conf.AttachStacktrace),
|
||||
SampleRate: float64(conf.SampleRate),
|
||||
EnableTracing: bool(conf.EnableTracing),
|
||||
TracesSampleRate: float64(conf.TracesSampleRate),
|
||||
ProfilesSampleRate: float64(conf.ProfilesSampleRate),
|
||||
IgnoreErrors: conf.IgnoreErrors,
|
||||
SendDefaultPII: bool(conf.SendDefaultPII),
|
||||
ServerName: string(conf.ServerName),
|
||||
Release: release,
|
||||
Environment: string(conf.Environment),
|
||||
MaxBreadcrumbs: int(conf.MaxBreadcrumbs),
|
||||
MaxSpans: int(conf.MaxSpans),
|
||||
MaxErrorDepth: int(conf.MaxErrorDepth),
|
||||
DebugWriter: loggerWriter.NewWriter(ctx, logger.LevelDebug),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
flush := func() {
|
||||
sentry.Flush(time.Duration(*conf.FlushTimeout))
|
||||
}
|
||||
|
||||
return flush, nil
|
||||
}
|
Reference in New Issue
Block a user