geck-go/logging/sentry.go
2021-12-02 09:16:01 +01:00

144 lines
2.7 KiB
Go

package logging
import (
"io"
"time"
"unsafe"
"github.com/buger/jsonparser"
"github.com/getsentry/sentry-go"
"github.com/rs/zerolog"
)
var levelMapping = map[zerolog.Level]sentry.Level{
zerolog.DebugLevel: sentry.LevelDebug,
zerolog.InfoLevel: sentry.LevelInfo,
zerolog.WarnLevel: sentry.LevelWarning,
zerolog.ErrorLevel: sentry.LevelError,
zerolog.FatalLevel: sentry.LevelFatal,
zerolog.PanicLevel: sentry.LevelFatal,
}
var now = time.Now
var _ = io.WriteCloser(new(SentryWriter))
type SentryWriter struct {
hub *sentry.Hub
levels map[zerolog.Level]struct{}
}
func NewSentryWriter() *SentryWriter {
lvls := []zerolog.Level{
zerolog.ErrorLevel,
zerolog.FatalLevel,
zerolog.PanicLevel,
}
levels := make(map[zerolog.Level]struct{}, len(lvls))
for _, lvl := range lvls {
levels[lvl] = struct{}{}
}
return &SentryWriter{
hub: sentry.CurrentHub(),
levels: levels,
}
}
func (p *SentryWriter) Write(data []byte) (int, error) {
event, ok := p.parseLogEvent(data)
if ok {
p.hub.CaptureEvent(event)
}
return len(data), nil
}
func (p *SentryWriter) Close() error {
return nil
}
func (p *SentryWriter) parseLogEvent(data []byte) (*sentry.Event, bool) {
const logger = "zerolog"
lvlStr, err := jsonparser.GetUnsafeString(data, zerolog.LevelFieldName)
if err != nil {
return nil, false
}
lvl, err := zerolog.ParseLevel(lvlStr)
if err != nil {
return nil, false
}
_, enabled := p.levels[lvl]
if !enabled {
return nil, false
}
sentryLvl, ok := levelsMapping[lvl]
if !ok {
return nil, false
}
event := sentry.Event{
Timestamp: now(),
Level: sentryLvl,
Logger: logger,
}
err = jsonparser.ObjectEach(data, func(key, value []byte, vt jsonparser.ValueType, offset int) error {
switch string(key) {
case zerolog.MessageFieldName:
event.Message = bytesToStrUnsafe(value)
case zerolog.ErrorFieldName:
event.Exception = append(event.Exception, sentry.Exception{
Value: bytesToStrUnsafe(value),
Stacktrace: newStacktrace(),
})
}
return nil
})
if err != nil {
return nil, false
}
return &event, true
}
func newStacktrace() *sentry.Stacktrace {
const (
currentModule = "git.joco.dk/joco/geck-go"
zerologModule = "github.com/rs/zerolog"
)
st := sentry.NewStacktrace()
threshold := len(st.Frames) - 1
for ; threshold > 0 && st.Frames[threshold].Module == currentModule; threshold-- {
}
outer:
for i := threshold; i > 0; i-- {
if st.Frames[i].Module == zerologModule {
for j := i - 1; j >= 0; j-- {
if st.Frames[j].Module != zerologModule {
threshold = j
break outer
}
}
break
}
}
st.Frames = st.Frames[:threshold+1]
return st
}
func bytesToStrUnsafe(data []byte) string {
return *(*string)(unsafe.Pointer(&data))
}