sentry.go 3.57 KiB
package orusapi
import (
"bytes"
"fmt"
"io"
"github.com/getsentry/sentry-go"
jsoniter "github.com/json-iterator/go"
)
// SentryOptions ...
type SentryOptions struct {
SentryDSN func(string) `long:"dsn" env:"DSN" ini-name:"dsn" description:"Sentry DSN"`
TagNames []string `long:"tag-names" env:"TAG_NAMES" ini-name:"tag-names" description:"log properties that should be set as tags on the sentry event"`
}
// NewSentryOptions ...
func NewSentryOptions(loggingOptions *LoggingOptions) *SentryOptions {
sentryOptions := SentryOptions{}
sentryOptions.SentryDSN = func(dsn string) {
log := loggingOptions.Logger()
client, err := sentry.NewClient(sentry.ClientOptions{
Dsn: dsn,
})
if err != nil {
log.Err(err).Msg("Could not initialize sentry")
return
}
hub := sentry.NewHub(client, sentry.NewScope())
loggingOptions.AddLogWrapper(
func(next io.Writer) io.Writer {
return SentryLogger{hub, next, &sentryOptions}
})
// OnShutdown(func() {
//hub.Flush(time.Second)
//})
}
return &sentryOptions
}
// SentryLogger ...
type SentryLogger struct {
hub *sentry.Hub
next io.Writer
options *SentryOptions
}
var (
levelMarker = []byte(`"level":"`)
levelMarkerLen = len(levelMarker)
levelsFirstLetter = []byte(`wefp`)
)
func buildEventIfLevelGtWarn(tagNames []string, p []byte) *sentry.Event {
if i := bytes.Index(p, levelMarker); i != -1 {
first := p[i+levelMarkerLen]
if bytes.IndexByte(levelsFirstLetter, first) != -1 {
return buildEvent(tagNames, p)
}
}
return nil
}
func buildEvent(tagNames []string, 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:
for _, tag := range tagNames {
if tag == field {
rawvalue := iter.Read()
var value string
if s, ok := rawvalue.(string); ok {
value = s
} else {
value = fmt.Sprint(rawvalue)
}
if len(value) > 200 {
value = value[:200]
}
event.Tags[tag] = value
return true
}
}
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(l.options.TagNames, p); event != nil {
l.hub.CaptureEvent(event)
}
return l.next.Write(p)
}