Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ The SDK supports reporting errors and tracking application performance.
To get started, have a look at one of our [examples](_examples/):
- [Basic error instrumentation](_examples/basic/main.go)
- [Error and tracing for HTTP servers](_examples/http/main.go)
- [Local development debugging with Spotlight](_examples/spotlight/main.go)

We also provide a [complete API reference](https://pkg.go.dev/github.com/getsentry/sentry-go).

Expand Down
79 changes: 79 additions & 0 deletions _examples/spotlight/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// This is an example program that demonstrates Sentry Go SDK integration
// with Spotlight for local development debugging.
//
// Try it by running:
//
// go run main.go
//
// To actually report events to Sentry, set the DSN either by editing the
// appropriate line below or setting the environment variable SENTRY_DSN to
// match the DSN of your Sentry project.
//
// Before running this example, make sure Spotlight is running:
//
// npm install -g @spotlightjs/spotlight
// spotlight
//
// Then open http://localhost:8969 in your browser to see the Spotlight UI.
package main

import (
"context"
"errors"
"log"
"time"

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

func main() {
err := sentry.Init(sentry.ClientOptions{
// Either set your DSN here or set the SENTRY_DSN environment variable.
Dsn: "",
// Enable printing of SDK debug messages.
// Useful when getting started or trying to figure something out.
Debug: true,
// Enable Spotlight for local debugging.
Spotlight: true,
// Enable tracing to see performance data in Spotlight.
EnableTracing: true,
TracesSampleRate: 1.0,
})
if err != nil {
log.Fatalf("sentry.Init: %s", err)
}
// Flush buffered events before the program terminates.
// Set the timeout to the maximum duration the program can afford to wait.
defer sentry.Flush(2 * time.Second)

log.Println("Sending sample events to Spotlight...")

// Capture a simple message
sentry.CaptureMessage("Hello from Spotlight!")

// Capture an exception
sentry.CaptureException(errors.New("example error for Spotlight debugging"))

// Capture an event with additional context
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetTag("environment", "development")
scope.SetLevel(sentry.LevelWarning)
scope.SetContext("example", map[string]interface{}{
"feature": "spotlight_integration",
"version": "1.0.0",
})
sentry.CaptureMessage("Event with additional context")
})

// Performance monitoring example
span := sentry.StartSpan(context.Background(), "example.operation")
defer span.Finish()

span.SetData("example", "data")
childSpan := span.StartChild("child.operation")
// Simulate some work
time.Sleep(100 * time.Millisecond)
childSpan.Finish()

log.Println("Events sent! Check your Spotlight UI at http://localhost:8969")
}
19 changes: 19 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ type ClientOptions struct {
// IMPORTANT: to not ignore any status codes, the option should be an empty slice and not nil. The nil option is
// used for defaulting to 404 ignores.
TraceIgnoreStatusCodes [][]int
// Enable Spotlight for local development debugging.
// When enabled, events are sent to the local Spotlight sidecar.
// Default Spotlight URL is http://localhost:8969/
Spotlight bool
// SpotlightURL is the URL to send events to when Spotlight is enabled.
// Defaults to http://localhost:8969/stream
SpotlightURL string
// DisableTelemetryBuffer disables the telemetry buffer layer for prioritizing events and uses the old transport layer.
DisableTelemetryBuffer bool
}
Expand Down Expand Up @@ -341,6 +348,13 @@ func NewClient(options ClientOptions) (*Client, error) {
options.TraceIgnoreStatusCodes = [][]int{{404}}
}

// Check for Spotlight environment variable
if !options.Spotlight {
if spotlightEnv := os.Getenv("SENTRY_SPOTLIGHT"); spotlightEnv == "true" || spotlightEnv == "1" {
options.Spotlight = true
}
}

// SENTRYGODEBUG is a comma-separated list of key=value pairs (similar
// to GODEBUG). It is not a supported feature: recognized debug options
// may change any time.
Expand Down Expand Up @@ -408,6 +422,10 @@ func (client *Client) setupTransport() {
}
}

if opts.Spotlight {
transport = NewSpotlightTransport(transport)
}

transport.Configure(opts)
client.Transport = transport
}
Expand Down Expand Up @@ -466,6 +484,7 @@ func (client *Client) setupIntegrations() {
new(ignoreErrorsIntegration),
new(ignoreTransactionsIntegration),
new(globalTagsIntegration),
new(spotlightIntegration),
}

if client.options.Integrations != nil {
Expand Down
8 changes: 8 additions & 0 deletions hub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,10 @@ func TestHub_Flush(t *testing.T) {
if gotEvents[0].Message != wantEvent.Message {
t.Fatalf("expected message to be %v, got %v", wantEvent.Message, gotEvents[0].Message)
}

if transport.FlushCount() != 1 {
t.Fatalf("expected transport.Flush called 1 time, got %d", transport.FlushCount())
}
}

func TestHub_Flush_NoClient(t *testing.T) {
Expand Down Expand Up @@ -540,4 +544,8 @@ func TestHub_FlushWithContext(t *testing.T) {
if gotEvents[0].Message != wantEvent.Message {
t.Fatalf("expected message to be %v, got %v", wantEvent.Message, gotEvents[0].Message)
}

if transport.FlushCount() != 1 {
t.Fatalf("expected transport.FlushWithContext called 1 time, got %d", transport.FlushCount())
}
}
27 changes: 27 additions & 0 deletions integrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,30 @@ func loadEnvTags() map[string]string {
}
return tags
}

// ================================
// Spotlight Integration
// ================================

type spotlightIntegration struct{}

func (si *spotlightIntegration) Name() string {
return "Spotlight"
}

func (si *spotlightIntegration) SetupOnce(client *Client) {
// The spotlight integration doesn't add event processors.
// It works by wrapping the transport in setupTransport().
// This integration is mainly for completeness and debugging visibility.
if client.options.Spotlight {
DebugLogger.Printf("Spotlight integration enabled. Events will be sent to %s",
client.getSpotlightURL())
}
}

func (client *Client) getSpotlightURL() string {
if client.options.SpotlightURL != "" {
return client.options.SpotlightURL
}
return "http://localhost:8969/stream"
}
43 changes: 38 additions & 5 deletions mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ func (scope *MockScope) ApplyToEvent(event *Event, _ *EventHint, _ *Client) *Eve

// MockTransport implements [Transport] for use in tests.
type MockTransport struct {
mu sync.Mutex
events []*Event
lastEvent *Event
mu sync.Mutex
events []*Event
lastEvent *Event
flushCount int
closeCount int
}

func (t *MockTransport) Configure(_ ClientOptions) {}
Expand All @@ -38,12 +40,43 @@ func (t *MockTransport) SendEvent(event *Event) {
t.lastEvent = event
}
func (t *MockTransport) Flush(_ time.Duration) bool {
t.mu.Lock()
defer t.mu.Unlock()
t.flushCount++
return true
}
func (t *MockTransport) FlushWithContext(_ context.Context) bool {
t.mu.Lock()
defer t.mu.Unlock()
t.flushCount++
return true
}
func (t *MockTransport) FlushWithContext(_ context.Context) bool { return true }
func (t *MockTransport) Events() []*Event {
t.mu.Lock()
defer t.mu.Unlock()
return t.events
}
func (t *MockTransport) Close() {}
func (t *MockTransport) FlushCount() int {
t.mu.Lock()
defer t.mu.Unlock()
return t.flushCount
}
func (t *MockTransport) Close() {
t.mu.Lock()
defer t.mu.Unlock()
t.closeCount++
}
func (t *MockTransport) CloseCount() int {
t.mu.Lock()
defer t.mu.Unlock()
return t.closeCount
}

// ResetCounts resets the flush and close counters.
// Useful for multi-assertion tests that check operation counts at different stages.
func (t *MockTransport) ResetCounts() {
t.mu.Lock()
defer t.mu.Unlock()
t.flushCount = 0
t.closeCount = 0
}
Loading
Loading