diff --git a/go.mod b/go.mod index 36b138158c4344ab392ea8faf149aa561c54fa32_Z28ubW9k..e3263107bd445741ceea3577706c98b6bcb60f01_Z28ubW9k 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ require ( github.com/getsentry/sentry-go v0.7.0 + github.com/json-iterator/go v1.1.6 + github.com/orus-io/go-flags v1.4.0 github.com/rs/zerolog v1.19.0 github.com/stretchr/testify v1.6.1 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a diff --git a/go.sum b/go.sum index 36b138158c4344ab392ea8faf149aa561c54fa32_Z28uc3Vt..e3263107bd445741ceea3577706c98b6bcb60f01_Z28uc3Vt 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,7 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= @@ -84,4 +85,5 @@ github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -87,4 +89,5 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= @@ -93,6 +96,8 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/orus-io/go-flags v1.4.0 h1:eDbrM1iRPkT68HUJibvtli/xhXSst5FOj7EGTEOeeCA= +github.com/orus-io/go-flags v1.4.0/go.mod h1:DZNFUSWKy2JbxIQeDVY6onal2VQbdL9ug5ODZfawylw= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= diff --git a/logging.go b/logging.go index 36b138158c4344ab392ea8faf149aa561c54fa32_bG9nZ2luZy5nbw==..e3263107bd445741ceea3577706c98b6bcb60f01_bG9nZ2luZy5nbw== 100644 --- a/logging.go +++ b/logging.go @@ -39,6 +39,12 @@ *o.log = o.log.Output(out) } +// AddLogWrapper adds a log wrapper to the stack +func (o *LoggingOptions) AddLogWrapper(wrapper func(io.Writer) io.Writer) { + o.logWrappers = append(o.logWrappers, wrapper) + o.resetOutput() +} + // SetMinLoggingLevel makes sure the logging level is not under a given value func (o *LoggingOptions) SetMinLoggingLevel(level zerolog.Level) { if level < o.log.GetLevel() { diff --git a/sentry.go b/sentry.go new file mode 100644 index 0000000000000000000000000000000000000000..e3263107bd445741ceea3577706c98b6bcb60f01_c2VudHJ5Lmdv --- /dev/null +++ b/sentry.go @@ -0,0 +1,147 @@ +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 + } +}