From f08d1f6068fe3af44eac4d70f74a1ee9b9a84e36 Mon Sep 17 00:00:00 2001 From: Diaa Sami Date: Tue, 6 Feb 2024 20:26:49 +0100 Subject: [PATCH] composer: send error log messages to sentry --- cmd/osbuild-composer/main.go | 7 + .../getsentry/sentry-go/logrus/logrusentry.go | 229 ++++++++++++++++++ vendor/modules.txt | 1 + 3 files changed, 237 insertions(+) create mode 100644 vendor/github.com/getsentry/sentry-go/logrus/logrusentry.go diff --git a/cmd/osbuild-composer/main.go b/cmd/osbuild-composer/main.go index 6f294ab729..ebf5fe307a 100644 --- a/cmd/osbuild-composer/main.go +++ b/cmd/osbuild-composer/main.go @@ -7,6 +7,7 @@ import ( "github.com/coreos/go-systemd/activation" "github.com/getsentry/sentry-go" + sentrylogrus "github.com/getsentry/sentry-go/logrus" slogger "github.com/osbuild/osbuild-composer/pkg/splunk_logger" "github.com/sirupsen/logrus" ) @@ -78,6 +79,12 @@ func main() { if err != nil { panic(err) } + + sentryhook := sentrylogrus.NewFromClient([]logrus.Level{logrus.PanicLevel, + logrus.FatalLevel, logrus.ErrorLevel}, + sentry.CurrentHub().Client()) + logrus.AddHook(sentryhook) + } else { logrus.Warn("GLITCHTIP_DSN not configured, skipping initializing Sentry/Glitchtip") } diff --git a/vendor/github.com/getsentry/sentry-go/logrus/logrusentry.go b/vendor/github.com/getsentry/sentry-go/logrus/logrusentry.go new file mode 100644 index 0000000000..b4695f0c9d --- /dev/null +++ b/vendor/github.com/getsentry/sentry-go/logrus/logrusentry.go @@ -0,0 +1,229 @@ +// Package sentrylogrus provides a simple Logrus hook for Sentry. +package sentrylogrus + +import ( + "errors" + "net/http" + "time" + + sentry "github.com/getsentry/sentry-go" + "github.com/sirupsen/logrus" +) + +// The identifier of the Logrus SDK. +const sdkIdentifier = "sentry.go.logrus" + +// These default log field keys are used to pass specific metadata in a way that +// Sentry understands. If they are found in the log fields, and the value is of +// the expected datatype, it will be converted from a generic field, into Sentry +// metadata. +// +// These keys may be overridden by calling SetKey on the hook object. +const ( + // FieldRequest holds an *http.Request. + FieldRequest = "request" + // FieldUser holds a User or *User value. + FieldUser = "user" + // FieldTransaction holds a transaction ID as a string. + FieldTransaction = "transaction" + // FieldFingerprint holds a string slice ([]string), used to dictate the + // grouping of this event. + FieldFingerprint = "fingerprint" + + // These fields are simply omitted, as they are duplicated by the Sentry SDK. + FieldGoVersion = "go_version" + FieldMaxProcs = "go_maxprocs" +) + +// Hook is the logrus hook for Sentry. +// +// It is not safe to configure the hook while logging is happening. Please +// perform all configuration before using it. +type Hook struct { + hub *sentry.Hub + fallback FallbackFunc + keys map[string]string + levels []logrus.Level +} + +var _ logrus.Hook = &Hook{} + +// New initializes a new Logrus hook which sends logs to a new Sentry client +// configured according to opts. +func New(levels []logrus.Level, opts sentry.ClientOptions) (*Hook, error) { + client, err := sentry.NewClient(opts) + if err != nil { + return nil, err + } + + client.SetSDKIdentifier(sdkIdentifier) + + return NewFromClient(levels, client), nil +} + +// NewFromClient initializes a new Logrus hook which sends logs to the provided +// sentry client. +func NewFromClient(levels []logrus.Level, client *sentry.Client) *Hook { + h := &Hook{ + levels: levels, + hub: sentry.NewHub(client, sentry.NewScope()), + keys: make(map[string]string), + } + return h +} + +// AddTags adds tags to the hook's scope. +func (h *Hook) AddTags(tags map[string]string) { + h.hub.Scope().SetTags(tags) +} + +// A FallbackFunc can be used to attempt to handle any errors in logging, before +// resorting to Logrus's standard error reporting. +type FallbackFunc func(*logrus.Entry) error + +// SetFallback sets a fallback function, which will be called in case logging to +// sentry fails. In case of a logging failure in the Fire() method, the +// fallback function is called with the original logrus entry. If the +// fallback function returns nil, the error is considered handled. If it returns +// an error, that error is passed along to logrus as the return value from the +// Fire() call. If no fallback function is defined, a default error message is +// returned to Logrus in case of failure to send to Sentry. +func (h *Hook) SetFallback(fb FallbackFunc) { + h.fallback = fb +} + +// SetKey sets an alternate field key. Use this if the default values conflict +// with other loggers, for instance. You may pass "" for new, to unset an +// existing alternate. +func (h *Hook) SetKey(oldKey, newKey string) { + if oldKey == "" { + return + } + if newKey == "" { + delete(h.keys, oldKey) + return + } + delete(h.keys, newKey) + h.keys[oldKey] = newKey +} + +func (h *Hook) key(key string) string { + if val := h.keys[key]; val != "" { + return val + } + return key +} + +// Levels returns the list of logging levels that will be sent to +// Sentry. +func (h *Hook) Levels() []logrus.Level { + return h.levels +} + +// Fire sends entry to Sentry. +func (h *Hook) Fire(entry *logrus.Entry) error { + event := h.entryToEvent(entry) + if id := h.hub.CaptureEvent(event); id == nil { + if h.fallback != nil { + return h.fallback(entry) + } + return errors.New("failed to send to sentry") + } + return nil +} + +var levelMap = map[logrus.Level]sentry.Level{ + logrus.TraceLevel: sentry.LevelDebug, + logrus.DebugLevel: sentry.LevelDebug, + logrus.InfoLevel: sentry.LevelInfo, + logrus.WarnLevel: sentry.LevelWarning, + logrus.ErrorLevel: sentry.LevelError, + logrus.FatalLevel: sentry.LevelFatal, + logrus.PanicLevel: sentry.LevelFatal, +} + +func (h *Hook) entryToEvent(l *logrus.Entry) *sentry.Event { + data := make(logrus.Fields, len(l.Data)) + for k, v := range l.Data { + data[k] = v + } + s := &sentry.Event{ + Level: levelMap[l.Level], + Extra: data, + Message: l.Message, + Timestamp: l.Time, + } + key := h.key(FieldRequest) + if req, ok := s.Extra[key].(*http.Request); ok { + delete(s.Extra, key) + s.Request = sentry.NewRequest(req) + } + if err, ok := s.Extra[logrus.ErrorKey].(error); ok { + delete(s.Extra, logrus.ErrorKey) + ex := h.exceptions(err) + s.Exception = ex + } + key = h.key(FieldUser) + if user, ok := s.Extra[key].(sentry.User); ok { + delete(s.Extra, key) + s.User = user + } + if user, ok := s.Extra[key].(*sentry.User); ok { + delete(s.Extra, key) + s.User = *user + } + key = h.key(FieldTransaction) + if txn, ok := s.Extra[key].(string); ok { + delete(s.Extra, key) + s.Transaction = txn + } + key = h.key(FieldFingerprint) + if fp, ok := s.Extra[key].([]string); ok { + delete(s.Extra, key) + s.Fingerprint = fp + } + delete(s.Extra, FieldGoVersion) + delete(s.Extra, FieldMaxProcs) + return s +} + +func (h *Hook) exceptions(err error) []sentry.Exception { + if !h.hub.Client().Options().AttachStacktrace { + return []sentry.Exception{{ + Type: "error", + Value: err.Error(), + }} + } + excs := []sentry.Exception{} + var last *sentry.Exception + for ; err != nil; err = errors.Unwrap(err) { + exc := sentry.Exception{ + Type: "error", + Value: err.Error(), + Stacktrace: sentry.ExtractStacktrace(err), + } + if last != nil && exc.Value == last.Value { + if last.Stacktrace == nil { + last.Stacktrace = exc.Stacktrace + continue + } + if exc.Stacktrace == nil { + continue + } + } + excs = append(excs, exc) + last = &excs[len(excs)-1] + } + // reverse + for i, j := 0, len(excs)-1; i < j; i, j = i+1, j-1 { + excs[i], excs[j] = excs[j], excs[i] + } + return excs +} + +// Flush waits until the underlying Sentry transport sends any buffered events, +// blocking for at most the given timeout. It returns false if the timeout was +// reached, in which case some events may not have been sent. +func (h *Hook) Flush(timeout time.Duration) bool { + return h.hub.Client().Flush(timeout) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index df639d8d4b..51d7890085 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -499,6 +499,7 @@ github.com/getsentry/sentry-go/internal/otel/baggage github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage github.com/getsentry/sentry-go/internal/ratelimit github.com/getsentry/sentry-go/internal/traceparser +github.com/getsentry/sentry-go/logrus # github.com/ghodss/yaml v1.0.0 ## explicit github.com/ghodss/yaml