Skip to content
Snippets Groups Projects
sentry.go 3.28 KiB
Newer Older
Christophe de Vienne's avatar
Christophe de Vienne committed
package orusapi

import (
	"bytes"
	"io"
	"time"

	"github.com/getsentry/sentry-go"
	jsoniter "github.com/json-iterator/go"
	"github.com/rs/zerolog/log"
)

// SentryOptions ...
type SentryOptions struct {
	SentryDSN func(string) `long:"dsn" env:"DSN" ini-name:"dsn" description:"Sentry DSN"`

	client *sentry.Client
	hub    *sentry.Hub
}

// GetClient returns the sentry client
func (o SentryOptions) GetClient() *sentry.Client {
	return o.client
}

// GetHub returns the sentry hub
func (o SentryOptions) GetHub() *sentry.Hub {
	return o.hub
}

// SentryLogger ...
type SentryLogger struct {
	hub  *sentry.Hub
	next io.Writer
}

var (
	levelMarker       = []byte(`"level":"`)
	levelMarkerLen    = len(levelMarker)
	levelsFirstLetter = []byte(`wefp`)
)

func buildEventIfLevelGtWarn(p []byte) *sentry.Event {
	if i := bytes.Index(p, levelMarker); i != -1 {
		first := p[i+levelMarkerLen]
		if bytes.IndexByte(levelsFirstLetter, first) != -1 {
			return buildEvent(p)
		}
	}
	return nil
}

func buildEvent(p []byte) *sentry.Event {
	iter := jsoniter.ConfigFastest.BorrowIterator(p)
	defer jsoniter.ConfigFastest.ReturnIterator(iter)

	if iter.WhatIsNext() != jsoniter.ObjectValue {
		return nil
	}
	event := sentry.NewEvent()

	if !iter.ReadObjectCB(func(iter *jsoniter.Iterator, field string) bool {
		switch field {
		case "message":
			event.Message = iter.ReadString()
		case "error":
			errMsg := iter.ReadString()
			if event.Message == "" {
				event.Message = errMsg
			}
			event.Extra["error"] = errMsg
		case "level":
			level := iter.ReadString()
			if level == "warn" {
				event.Level = sentry.LevelWarning
			} else {
				event.Level = sentry.Level(level)
			}
		case "request":
			iter.ReadVal(&event.Request)
		case "user":
			if !iter.ReadObjectCB(func(iter *jsoniter.Iterator, field string) bool {
				switch field {
				case "id":
					event.User.ID = iter.ReadString()
				default:
					event.Extra["user."+field] = iter.Read()
				}
				return true
			}) {
				return false
			}
		case "exception":
			var e sentry.Exception
			iter.ReadVal(&e)
			event.Exception = []sentry.Exception{e}
		default:
			event.Extra[field] = iter.Read()
		}
		return true
	}) {
		return nil
	}
	if op, ok := event.Extra["api-operation-id"]; ok {
		tag, _ := event.Extra["api-tag"].(string)
		event.Message = tag + "/" + op.(string) + ": " + event.Message
	} else if event.Request != nil && event.Request.Method != "" {
		event.Message = event.Request.Method + " " + event.Request.URL + ": " + event.Message
	}
	return event
}

// Write ...
func (l SentryLogger) Write(p []byte) (n int, err error) {
	if event := buildEventIfLevelGtWarn(p); event != nil {
		l.hub.CaptureEvent(event)
	}
	return l.next.Write(p)
}

// Setup ...
func (o *SentryOptions) Setup(loggingOptions *LoggingOptions, environment *string) {
	o.SentryDSN = func(dsn string) {
		client, err := sentry.NewClient(sentry.ClientOptions{
			Environment: *environment,
			Dsn:         dsn,
		})
		if err != nil {
			log.Err(err).Msg("Could not initialize sentry")
			return
		}
		o.client = client
		o.hub = sentry.NewHub(client, sentry.NewScope())
		loggingOptions.AddLogWrapper(func(next io.Writer) io.Writer {
			return SentryLogger{o.hub, next}
		})
	}
}

// Shutdown ...
func (o SentryOptions) Shutdown() {
	if o.hub != nil {
		o.hub.Flush(time.Second)
		o.hub = nil
		o.client = nil
	}
}