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