Skip to content

Commit

Permalink
Add HubProvider functionality to the logrus integration (#936)
Browse files Browse the repository at this point in the history
  • Loading branch information
ribice authored Jan 2, 2025
1 parent 486c78c commit 066c69e
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 18 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@

Add ability to override `hub` in `context` for integrations that use custom context ([#931](https://github.com/getsentry/sentry-go/pull/931))

- Add `HubProvider` Hook for `sentrylogrus`, enabling dynamic Sentry hub allocation for each log entry or goroutine. ([#936](https://github.com/getsentry/sentry-go/pull/936))

This change enhances compatibility with Sentry's recommendation of using separate hubs per goroutine. To ensure a separate Sentry hub for each goroutine, configure the `HubProvider` like this:

```go
hook, err := sentrylogrus.New(nil, sentry.ClientOptions{})
if err != nil {
log.Fatalf("Failed to initialize Sentry hook: %v", err)
}

// Set a custom HubProvider to generate a new hub for each goroutine or log entry
hook.SetHubProvider(func() *sentry.Hub {
client, _ := sentry.NewClient(sentry.ClientOptions{})
return sentry.NewHub(client, sentry.NewScope())
})

logrus.AddHook(hook)
```


## 0.30.0

The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.30.0.
Expand Down
44 changes: 30 additions & 14 deletions logrus/logrusentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ const (
// 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
hubProvider func() *sentry.Hub
fallback FallbackFunc
keys map[string]string
levels []logrus.Level
}

var _ logrus.Hook = &Hook{}
Expand All @@ -70,17 +70,26 @@ func New(levels []logrus.Level, opts sentry.ClientOptions) (*Hook, error) {
// 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{
defaultHub := sentry.NewHub(client, sentry.NewScope())
return &Hook{
levels: levels,
hub: sentry.NewHub(client, sentry.NewScope()),
keys: make(map[string]string),
hubProvider: func() *sentry.Hub {
// Default to using the same hub if no specific provider is set
return defaultHub
},
keys: make(map[string]string),
}
return h
}

// SetHubProvider sets a function to provide a hub for each log entry.
// This can be used to ensure separate hubs per goroutine if needed.
func (h *Hook) SetHubProvider(provider func() *sentry.Hub) {
h.hubProvider = provider
}

// AddTags adds tags to the hook's scope.
func (h *Hook) AddTags(tags map[string]string) {
h.hub.Scope().SetTags(tags)
h.hubProvider().Scope().SetTags(tags)
}

// A FallbackFunc can be used to attempt to handle any errors in logging, before
Expand Down Expand Up @@ -128,8 +137,9 @@ func (h *Hook) Levels() []logrus.Level {

// Fire sends entry to Sentry.
func (h *Hook) Fire(entry *logrus.Entry) error {
hub := h.hubProvider() // Use the hub provided by the HubProvider
event := h.entryToEvent(entry)
if id := h.hub.CaptureEvent(event); id == nil {
if id := hub.CaptureEvent(event); id == nil {
if h.fallback != nil {
return h.fallback(entry)
}
Expand Down Expand Up @@ -160,34 +170,40 @@ func (h *Hook) entryToEvent(l *logrus.Entry) *sentry.Event {
Timestamp: l.Time,
Logger: name,
}

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)
s.SetException(err, -1)
}

key = h.key(FieldUser)
if user, ok := s.Extra[key].(sentry.User); ok {
switch user := s.Extra[key].(type) {
case sentry.User:
delete(s.Extra, key)
s.User = user
}
if user, ok := s.Extra[key].(*sentry.User); ok {
case *sentry.User:
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
Expand All @@ -197,5 +213,5 @@ func (h *Hook) entryToEvent(l *logrus.Entry) *sentry.Event {
// 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)
return h.hubProvider().Client().Flush(timeout)
}
40 changes: 36 additions & 4 deletions logrus/logrusentry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
"testing"
"time"

pkgerr "github.com/pkg/errors"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
pkgerr "github.com/pkg/errors"
"github.com/sirupsen/logrus"

"github.com/getsentry/sentry-go"
Expand All @@ -33,15 +34,39 @@ func TestNew(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if id := h.hub.CaptureEvent(&sentry.Event{}); id == nil {
if id := h.hubProvider().CaptureEvent(&sentry.Event{}); id == nil {
t.Error("CaptureEvent failed")
}
if !h.Flush(testutils.FlushTimeout()) {
if !h.hubProvider().Client().Flush(testutils.FlushTimeout()) {
t.Error("flush failed")
}
})
}

func TestSetHubProvider(t *testing.T) {
t.Parallel()

h, err := New(nil, sentry.ClientOptions{})
if err != nil {
t.Fatal(err)
}

// Custom HubProvider to ensure separate hubs for each test
h.SetHubProvider(func() *sentry.Hub {
client, _ := sentry.NewClient(sentry.ClientOptions{})
return sentry.NewHub(client, sentry.NewScope())
})

entry := &logrus.Entry{Level: logrus.ErrorLevel}
if err := h.Fire(entry); err != nil {
t.Fatal(err)
}

if !h.hubProvider().Client().Flush(testutils.FlushTimeout()) {
t.Error("flush failed")
}
}

func TestFire(t *testing.T) {
t.Parallel()

Expand All @@ -54,12 +79,13 @@ func TestFire(t *testing.T) {
if err != nil {
t.Fatal(err)
}

err = hook.Fire(entry)
if err != nil {
t.Fatal(err)
}

if !hook.Flush(testutils.FlushTimeout()) {
if !hook.hubProvider().Client().Flush(testutils.FlushTimeout()) {
t.Error("flush failed")
}
}
Expand Down Expand Up @@ -262,6 +288,12 @@ func Test_entryToEvent(t *testing.T) {
t.Fatal(err)
}

// Custom HubProvider for test environment
h.SetHubProvider(func() *sentry.Hub {
client, _ := sentry.NewClient(sentry.ClientOptions{})
return sentry.NewHub(client, sentry.NewScope())
})

for name, tt := range tests {
tt := tt
t.Run(name, func(t *testing.T) {
Expand Down

0 comments on commit 066c69e

Please sign in to comment.