Skip to content

Implement flush with context #935

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

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
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
36 changes: 36 additions & 0 deletions _examples/flush-with-context/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"context"
"fmt"
"time"

"github.com/getsentry/sentry-go"
)

func main() {
_ = sentry.Init(sentry.ClientOptions{
Dsn: "https://[email protected]/1337",
Debug: true,
})

sentry.CaptureMessage("Event #1")
sentry.CaptureMessage("Event #2")
sentry.CaptureMessage("Event #3")

go func() {
sentry.CaptureMessage("Event #4")
sentry.CaptureMessage("Event #5")
}()

fmt.Println("=> Flushing transport buffer")

ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()

if sentry.FlushWithContext(ctx) {
fmt.Println("=> All queued events delivered!")
} else {
fmt.Println("=> Flush timeout reached")
}
}
19 changes: 19 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,25 @@ func (client *Client) Flush(timeout time.Duration) bool {
return client.Transport.Flush(timeout)
}

// FlushWithContext waits until the underlying Transport sends any buffered events
// to the Sentry server, blocking for at most the duration specified by the context.
// It returns false if the context is canceled before the events are sent. In such a case,
// some events may not be delivered.
//
// FlushWithContext should be called before terminating the program to ensure no
// events are unintentionally dropped.
//
// Avoid calling FlushWithContext indiscriminately after each call to CaptureEvent,
// CaptureException, or CaptureMessage. To send events synchronously over the network,
// configure the SDK to use HTTPSyncTransport during initialization with Init.

func (client *Client) FlushWithContext(ctx context.Context) bool {
if client.batchLogger != nil {
client.batchLogger.Flush()
}
return client.Transport.FlushWithContext(ctx)
}

// Close clean up underlying Transport resources.
//
// Close should be called after Flush and before terminating the program
Expand Down
22 changes: 22 additions & 0 deletions hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,28 @@
return client.Flush(timeout)
}

// FlushWithContext waits until the underlying Transport sends any buffered events
// to the Sentry server, blocking for at most the duration specified by the context.
// It returns false if the context is canceled before the events are sent. In such a case,
// some events may not be delivered.
//
// FlushWithContext should be called before terminating the program to ensure no
// events are unintentionally dropped.
//
// Avoid calling FlushWithContext indiscriminately after each call to CaptureEvent,
// CaptureException, or CaptureMessage. To send events synchronously over the network,
// configure the SDK to use HTTPSyncTransport during initialization with Init.

func (hub *Hub) FlushWithContext(ctx context.Context) bool {
client := hub.Client()

if client == nil {
return false
}

Check warning on line 385 in hub.go

View check run for this annotation

Codecov / codecov/patch

hub.go#L384-L385

Added lines #L384 - L385 were not covered by tests

return client.FlushWithContext(ctx)
}

// GetTraceparent returns the current Sentry traceparent string, to be used as a HTTP header value
// or HTML meta tag value.
// This function is context aware, as in it either returns the traceparent based
Expand Down
40 changes: 40 additions & 0 deletions hub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"sync"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
Expand Down Expand Up @@ -493,3 +494,42 @@ func TestConcurrentHubClone(t *testing.T) {
t.Errorf("Events mismatch (-want +got):\n%s", diff)
}
}

func TestHub_Flush(t *testing.T) {
hub, client, _ := setupHubTest()
transport := &MockTransport{}
client.Transport = transport

wantEvent := Event{Message: "something"}
hub.CaptureEvent(&wantEvent)
hub.Flush(20 * time.Millisecond)

gotEvents := transport.Events()
if len(gotEvents) != 1 {
t.Fatalf("expected 1 event, got %d", len(gotEvents))
}
if gotEvents[0].Message != wantEvent.Message {
t.Fatalf("expected message to be %v, got %v", wantEvent.Message, gotEvents[0].Message)
}
}

func TestHub_FlushWithContext(t *testing.T) {
hub, client, _ := setupHubTest()
transport := &MockTransport{}
client.Transport = transport

wantEvent := Event{Message: "something"}
hub.CaptureEvent(&wantEvent)

cancelCtx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
hub.FlushWithContext(cancelCtx)
defer cancel()

gotEvents := transport.Events()
if len(gotEvents) != 1 {
t.Fatalf("expected 1 event, got %d", len(gotEvents))
}
if gotEvents[0].Message != wantEvent.Message {
t.Fatalf("expected message to be %v, got %v", wantEvent.Message, gotEvents[0].Message)
}
}
15 changes: 15 additions & 0 deletions log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,21 @@ func Test_batchLogger_Flush(t *testing.T) {
}
}

func Test_batchLogger_FlushWithContext(t *testing.T) {
ctx, mockTransport := setupMockTransport()
l := NewLogger(context.Background())
l.Info(ctx, "context done log")

cancelCtx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
FlushWithContext(cancelCtx)
defer cancel()

events := mockTransport.Events()
if len(events) != 1 {
t.Fatalf("expected 1 event, got %d", len(events))
}
}

func Test_sentryLogger_BeforeSendLog(t *testing.T) {
ctx := context.Background()
mockTransport := &MockTransport{}
Expand Down
2 changes: 1 addition & 1 deletion logrus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,5 @@ This ensures that logs from specific contexts or threads use the appropriate Sen

## Notes

- Always call Flush to ensure all events are sent to Sentry before program termination
- Always call `Flush` or `FlushWithContext` to ensure all events are sent to Sentry before program termination

9 changes: 9 additions & 0 deletions logrus/logrusentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package sentrylogrus

import (
"context"
"errors"
"net/http"
"time"
Expand Down Expand Up @@ -222,3 +223,11 @@
func (h *Hook) Flush(timeout time.Duration) bool {
return h.hubProvider().Client().Flush(timeout)
}

// FlushWithContext waits for the underlying Sentry transport to send any buffered
// events, blocking until the context's deadline is reached or the context is canceled.
// It returns false if the context is canceled or its deadline expires before the events
// are sent, meaning some events may not have been sent.
func (h *Hook) FlushWithContext(ctx context.Context) bool {
return h.hubProvider().Client().FlushWithContext(ctx)

Check warning on line 232 in logrus/logrusentry.go

View check run for this annotation

Codecov / codecov/patch

logrus/logrusentry.go#L231-L232

Added lines #L231 - L232 were not covered by tests
}
2 changes: 2 additions & 0 deletions mocks.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sentry

import (
"context"
"sync"
"time"
)
Expand Down Expand Up @@ -39,6 +40,7 @@ func (t *MockTransport) SendEvent(event *Event) {
func (t *MockTransport) Flush(_ time.Duration) bool {
return true
}
func (t *MockTransport) FlushWithContext(_ context.Context) bool { return true }
func (t *MockTransport) Events() []*Event {
t.mu.Lock()
defer t.mu.Unlock()
Expand Down
17 changes: 17 additions & 0 deletions sentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,23 @@ func Flush(timeout time.Duration) bool {
return hub.Flush(timeout)
}

// FlushWithContext waits until the underlying Transport sends any buffered events
// to the Sentry server, blocking for at most the duration specified by the context.
// It returns false if the context is canceled before the events are sent. In such a case,
// some events may not be delivered.
//
// FlushWithContext should be called before terminating the program to ensure no
// events are unintentionally dropped.
//
// Avoid calling FlushWithContext indiscriminately after each call to CaptureEvent,
// CaptureException, or CaptureMessage. To send events synchronously over the network,
// configure the SDK to use HTTPSyncTransport during initialization with Init.

func FlushWithContext(ctx context.Context) bool {
hub := CurrentHub()
return hub.FlushWithContext(ctx)
}

// LastEventID returns an ID of last captured event.
func LastEventID() EventID {
hub := CurrentHub()
Expand Down
2 changes: 1 addition & 1 deletion slog/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,4 @@ handler := slogSentry.Option{

## Notes

- Always call Flush to ensure all events are sent to Sentry before program termination
- Always call `Flush` or `FlushWithContext` to ensure all events are sent to Sentry before program termination
30 changes: 26 additions & 4 deletions transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
// Transport is used by the Client to deliver events to remote server.
type Transport interface {
Flush(timeout time.Duration) bool
FlushWithContext(ctx context.Context) bool
Configure(options ClientOptions)
SendEvent(event *Event)
Close()
Expand Down Expand Up @@ -439,15 +440,27 @@
// have the SDK send events over the network synchronously, configure it to use
// the HTTPSyncTransport in the call to Init.
func (t *HTTPTransport) Flush(timeout time.Duration) bool {
toolate := time.After(timeout)
timeoutCh := make(chan struct{})
time.AfterFunc(timeout, func() {
close(timeoutCh)
})
return t.flushInternal(timeoutCh)
}

// FlushWithContext works like Flush, but it accepts a context.Context instead of a timeout.
func (t *HTTPTransport) FlushWithContext(ctx context.Context) bool {
return t.flushInternal(ctx.Done())

Check warning on line 452 in transport.go

View check run for this annotation

Codecov / codecov/patch

transport.go#L451-L452

Added lines #L451 - L452 were not covered by tests
}

func (t *HTTPTransport) flushInternal(timeout <-chan struct{}) bool {
// Wait until processing the current batch has started or the timeout.
//
// We must wait until the worker has seen the current batch, because it is
// the only way b.done will be closed. If we do not wait, there is a
// possible execution flow in which b.done is never closed, and the only way
// out of Flush would be waiting for the timeout, which is undesired.
var b batch

for {
select {
case b = <-t.buffer:
Expand All @@ -457,7 +470,7 @@
default:
t.buffer <- b
}
case <-toolate:
case <-timeout:
goto fail
}
}
Expand All @@ -478,12 +491,12 @@
case <-b.done:
DebugLogger.Println("Buffer flushed successfully.")
return true
case <-toolate:
case <-timeout:
goto fail
}

fail:
DebugLogger.Println("Buffer flushing reached the timeout.")
DebugLogger.Println("Buffer flushing was canceled or timed out.")
return false
}

Expand Down Expand Up @@ -697,6 +710,11 @@
return true
}

// FlushWithContext is a no-op for HTTPSyncTransport. It always returns true immediately.
func (t *HTTPSyncTransport) FlushWithContext(_ context.Context) bool {
return true

Check warning on line 715 in transport.go

View check run for this annotation

Codecov / codecov/patch

transport.go#L714-L715

Added lines #L714 - L715 were not covered by tests
}

func (t *HTTPSyncTransport) disabled(c ratelimit.Category) bool {
t.mu.Lock()
defer t.mu.Unlock()
Expand Down Expand Up @@ -729,4 +747,8 @@
return true
}

func (noopTransport) FlushWithContext(context.Context) bool {
return true

Check warning on line 751 in transport.go

View check run for this annotation

Codecov / codecov/patch

transport.go#L750-L751

Added lines #L750 - L751 were not covered by tests
}

func (noopTransport) Close() {}
2 changes: 1 addition & 1 deletion zerolog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ The `sentryzerolog.Options` struct allows you to configure the following:

## Notes

- Always call Flush to ensure all events are sent to Sentry before program termination
- Always call `Flush` or `FlushWithContext` to ensure all events are sent to Sentry before program termination
Loading