Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HubProvider functionality for logrus integration #936

Merged
merged 6 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
// 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 @@
// 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)

Check warning on line 92 in logrus/logrusentry.go

View check run for this annotation

Codecov / codecov/patch

logrus/logrusentry.go#L92

Added line #L92 was not covered by tests
}

// A FallbackFunc can be used to attempt to handle any errors in logging, before
Expand Down Expand Up @@ -128,8 +137,9 @@

// 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 @@
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 @@
// 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)

Check warning on line 216 in logrus/logrusentry.go

View check run for this annotation

Codecov / codecov/patch

logrus/logrusentry.go#L216

Added line #L216 was not covered by tests
}
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
Loading