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