145 lines
2.7 KiB
Go
145 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))
|
||
|
}
|