Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop raven in favor of sentry-go #383

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/bsm/redislock v0.9.3
github.com/caarlos0/env/v10 v10.0.0
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d
github.com/dave/jennifer v1.4.1
github.com/getkin/kin-openapi v0.0.0-20180813063848-e1956e8013e5
github.com/getsentry/sentry-go v0.29.0
Expand All @@ -25,7 +24,6 @@ require (
github.com/jpillora/backoff v1.0.0
github.com/mattn/go-isatty v0.0.20
github.com/minio/minio-go/v7 v7.0.7
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v1.19.0
github.com/redis/go-redis/v9 v9.5.1
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ github.com/bsm/redislock v0.9.3/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDP
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
Expand Down
28 changes: 28 additions & 0 deletions internal/sentry/sentry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package sentry

import (
"log"
"os"

"github.com/getsentry/sentry-go"
)

func init() {
err := sentry.Init(sentry.ClientOptions{
Dsn: os.Getenv("SENTRY_DSN"),
Environment: os.Getenv("ENVIRONMENT"),
EnableTracing: true,
TracesSampleRate: 1.0,
BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
// Drop request body.
if event.Request != nil {
event.Request.Data = ""
}

return event
},
})
if err != nil {
log.Fatalf("sentry.Init: %v", err)
}
}
163 changes: 79 additions & 84 deletions maintenance/errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,43 @@ import (
"os"
"time"

"github.com/getsentry/sentry-go"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog"

"github.com/pace/bricks/http/jsonapi/runtime"
"github.com/pace/bricks/http/oauth2"
"github.com/pace/bricks/maintenance/errors/raven"
_ "github.com/pace/bricks/internal/sentry"
"github.com/pace/bricks/maintenance/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog"
)

var paceHTTPPanicCounter = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "pace_http_panic_total",
Help: "A counter for panics intercepted while handling a request",
})

var DefaultClient *sentry.Client

func init() {
prometheus.MustRegister(paceHTTPPanicCounter)
}

type ErrWithExtra struct {
err error
extra map[string]any
}

func NewErrWithExtra(err error, extra map[string]any) ErrWithExtra {
return ErrWithExtra{
err: err,
extra: extra,
}
}

func (e ErrWithExtra) Error() string {
return e.err.Error()
}

// PanicWrap wraps a panic for HandleRequest
type PanicWrap struct {
err interface{}
Expand Down Expand Up @@ -106,7 +126,7 @@ func HandleError(rp interface{}, handlerName string, w http.ResponseWriter, r *h
}
log.Stack(ctx)

sentryEvent{ctx, r, rp, 1, handlerName}.Send()
sentry.CaptureEvent(getEvent(ctx, r, rp, 1, handlerName))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we find a better variable name for rp? It seems to be type any. But it's hard to grasp.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also for other usages inside this file...


runtime.WriteError(w, http.StatusInternalServerError, errors.New("Internal Server Error"))
}
Expand All @@ -122,119 +142,94 @@ func Handle(ctx context.Context, rp interface{}) {
}
log.Stack(ctx)

sentryEvent{ctx, nil, rp, 1, ""}.Send()
}

// HandleWithCtx should be called with defer to recover panics in goroutines
func HandleWithCtx(ctx context.Context, handlerName string) {
if rp := recover(); rp != nil {
log.Ctx(ctx).Error().Str("handler", handlerName).Msgf("Panic: %v", rp)
log.Stack(ctx)

sentryEvent{ctx, nil, rp, 2, handlerName}.Send()
}
}

func HandleErrorNoStack(ctx context.Context, err error) {
log.Ctx(ctx).Info().Msgf("Received error, will not handle further: %v", err)
}

// New returns an error that formats as the given text.
func New(text string) error {
return errors.New(text)
}

// WrapWithExtra adds extra data to an error before reporting to Sentry
func WrapWithExtra(err error, extraInfo map[string]interface{}) error {
return raven.WrapWithExtra(err, extraInfo)
}

type sentryEvent struct {
ctx context.Context
req *http.Request // optional
r interface{}
level int
handlerName string
}

func (e sentryEvent) Send() {
_, errCh := raven.Capture(e.build(), nil)
<-errCh // ensure the message get send even if the main goroutine is about to stop
sentry.CaptureEvent(getEvent(ctx, nil, rp, 1, ""))
}

func (e sentryEvent) build() *raven.Packet {
ctx, r, rp, handlerName := e.ctx, e.req, e.r, e.handlerName

func getEvent(ctx context.Context, r *http.Request, rp any, level int, handlerName string) *sentry.Event {
// get request from context if available
if r == nil {
r = requestFromContext(ctx)
}

rvalStr := fmt.Sprint(rp)
var packet *raven.Packet

event := sentry.NewEvent()

if err, ok := rp.(error); ok {
stack := raven.GetOrNewStacktrace(err, 2+e.level, 3, nil)
packet = raven.NewPacket(rvalStr, raven.NewException(err, stack))
event.SetException(err, level)
} else {
stack := raven.NewStacktrace(2+e.level, 3, nil)
packet = raven.NewPacket(rvalStr, raven.NewException(errors.New(rvalStr), stack))
}

// extract ErrWithExtra info and append it to the packet
if ee, ok := rp.(raven.ErrWithExtra); ok {
for k, v := range ee.ExtraInfo() {
packet.Extra[k] = v
}
event.SetException(errors.New(rvalStr), level)
}

// add user
userID, ok := oauth2.UserID(ctx)
user := raven.User{ID: userID}
if r != nil {
user.IP = log.ProxyAwareRemote(r)
}
packet.Interfaces = append(packet.Interfaces, &user)
if ok {
packet.Tags = append(packet.Tags, raven.Tag{Key: "user_id", Value: userID})
event.User.ID = userID
}

if r != nil {
event.User.IPAddress = log.ProxyAwareRemote(r)
}

// from context
if reqID := log.RequestIDFromContext(ctx); reqID != "" {
packet.Extra["req_id"] = reqID
packet.Tags = append(packet.Tags, raven.Tag{Key: "req_id", Value: reqID})
event.Extra["req_id"] = reqID
event.Tags["req_id"] = reqID
}

if traceID := log.TraceIDFromContext(ctx); traceID != "" {
packet.Extra["uber_trace_id"] = traceID
packet.Tags = append(packet.Tags, raven.Tag{Key: "trace_id", Value: traceID})
event.Extra["uber_trace_id"] = traceID
event.Tags["trace_id"] = traceID
}
packet.Extra["handler"] = handlerName

event.Extra["handler"] = handlerName

if clientID, ok := oauth2.ClientID(ctx); ok {
packet.Extra["oauth2_client_id"] = clientID
}
if scopes := oauth2.Scopes(ctx); len(scopes) > 0 {
packet.Extra["oauth2_scopes"] = scopes
event.Extra["oauth2_client_id"] = clientID
}

// from request
if r != nil {
packet.Interfaces = append(packet.Interfaces, raven.NewHttp(r))
if scopes := oauth2.Scopes(ctx); len(scopes) > 0 {
event.Extra["oauth2_scopes"] = scopes
}

// from env
packet.Extra["microservice"] = os.Getenv("JAEGER_SERVICE_NAME")
event.Extra["microservice"] = os.Getenv("JAEGER_SERVICE_NAME")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a side question: When will we be able to drop or change this env var? Because we don't actually use Jaeger anymore...


// add breadcrumbs
packet.Breadcrumbs = getBreadcrumbs(ctx)
event.Breadcrumbs = getBreadcrumbs(ctx)

return packet
return event
}

// HandleWithCtx should be called with defer to recover panics in goroutines
func HandleWithCtx(ctx context.Context, handlerName string) {
if rp := recover(); rp != nil {
log.Ctx(ctx).Error().Str("handler", handlerName).Msgf("Panic: %v", rp)
log.Stack(ctx)

sentry.CaptureEvent(getEvent(ctx, nil, rp, 2, handlerName))
}
}

func HandleErrorNoStack(ctx context.Context, err error) {
log.Ctx(ctx).Info().Msgf("Received error, will not handle further: %v", err)
}

// New returns an error that formats as the given text.
func New(text string) error {
return errors.New(text)
}

// WrapWithExtra adds extra data to an error before reporting to Sentry
func WrapWithExtra(err error, extraInfo map[string]interface{}) error {
return NewErrWithExtra(err, extraInfo)
}

// getBreadcrumbs takes a context and tries to extract the logs from it if it
// holds a log.Sink. If that's the case, the logs will all be translated
// to valid sentry breadcrumbs if possible. In case of a failure, the
// breadcrumbs will be dropped and a warning will be logged.
func getBreadcrumbs(ctx context.Context) []*raven.Breadcrumb {
func getBreadcrumbs(ctx context.Context) []*sentry.Breadcrumb {
sink, ok := log.SinkFromContext(ctx)
if !ok {
return nil
Expand All @@ -246,7 +241,7 @@ func getBreadcrumbs(ctx context.Context) []*raven.Breadcrumb {
return nil
}

result := make([]*raven.Breadcrumb, len(data))
result := make([]*sentry.Breadcrumb, len(data))
for i, d := range data {
crumb, err := createBreadcrumb(d)
if err != nil {
Expand All @@ -260,7 +255,7 @@ func getBreadcrumbs(ctx context.Context) []*raven.Breadcrumb {
return result
}

func createBreadcrumb(data map[string]interface{}) (*raven.Breadcrumb, error) {
func createBreadcrumb(data map[string]any) (*sentry.Breadcrumb, error) {
// remove the request id if it can still be found in the logs
delete(data, "req_id")

Expand Down Expand Up @@ -318,11 +313,11 @@ func createBreadcrumb(data map[string]interface{}) (*raven.Breadcrumb, error) {
typ = "error"
}

return &raven.Breadcrumb{
return &sentry.Breadcrumb{
Category: category,
Level: level,
Message: message,
Timestamp: time.Unix(),
Timestamp: time,
Type: typ,
Data: data,
}, nil
Expand All @@ -331,7 +326,7 @@ func createBreadcrumb(data map[string]interface{}) (*raven.Breadcrumb, error) {
// translateZerologLevelToSentryLevel takes in a zerolog.Level as string
// and returns the equivalent sentry breadcrumb level. If the given level
// can't be parsed to a valid zerolog.Level an error is returned.
func translateZerologLevelToSentryLevel(l string) (string, error) {
func translateZerologLevelToSentryLevel(l string) (sentry.Level, error) {
level, err := zerolog.ParseLevel(l)
if err != nil {
return "", err
Expand Down
Loading
Loading