Skip to content

Commit ea10ecb

Browse files
committed
Merge branch 'master' into feat/telemetry-scheduler
2 parents e0478a2 + c982e6f commit ea10ecb

File tree

24 files changed

+902
-150
lines changed

24 files changed

+902
-150
lines changed

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,53 @@
11
# Changelog
22

3+
## 0.36.1
4+
5+
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.36.1.
6+
7+
### Bug Fixes
8+
9+
- Prevent panic when converting error chains containing non-comparable error types by using a safe fallback for visited detection in exception conversion ([#1113](https://github.com/getsentry/sentry-go/pull/1113))
10+
311
## 0.36.0
412

13+
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.36.0.
14+
515
### Breaking Changes
616

717
- Behavioral change for the `MaxBreadcrumbs` client option. Removed the hard limit of 100 breadcrumbs, allowing users to set a larger limit and also changed the default limit from 30 to 100 ([#1106](https://github.com/getsentry/sentry-go/pull/1106)))
818

19+
- The changes to error handling ([#1075](https://github.com/getsentry/sentry-go/pull/1075)) will affect issue grouping. It is expected that any wrapped and complex errors will be grouped under a new issue group.
20+
21+
### Features
22+
23+
- Add support for improved issue grouping with enhanced error chain handling ([#1075](https://github.com/getsentry/sentry-go/pull/1075))
24+
25+
The SDK now provides better handling of complex error scenarios, particularly when dealing with multiple related errors or error chains. This feature automatically detects and properly structures errors created with Go's `errors.Join()` function and other multi-error patterns.
26+
27+
```go
28+
// Multiple errors are now properly grouped and displayed in Sentry
29+
err1 := errors.New("err1")
30+
err2 := errors.New("err2")
31+
combinedErr := errors.Join(err1, err2)
32+
33+
// When captured, these will be shown as related exceptions in Sentry
34+
sentry.CaptureException(combinedErr)
35+
```
36+
37+
- Add `TraceIgnoreStatusCodes` option to allow filtering of HTTP transactions based on status codes ([#1089](https://github.com/getsentry/sentry-go/pull/1089))
38+
- Configure which HTTP status codes should not be traced by providing single codes or ranges
39+
- Example: `TraceIgnoreStatusCodes: [][]int{{404}, {500, 599}}` ignores 404 and server errors 500-599
40+
41+
### Bug Fixes
42+
43+
- Fix logs being incorrectly filtered by `BeforeSend` callback ([#1109](https://github.com/getsentry/sentry-go/pull/1109))
44+
- Logs now bypass the `processEvent` method and are sent directly to the transport
45+
- This ensures logs are only filtered by `BeforeSendLog`, not by the error/message `BeforeSend` callback
46+
47+
### Misc
48+
49+
- Add support for Go 1.25 and drop support for Go 1.22 ([#1103](https://github.com/getsentry/sentry-go/pull/1103))
50+
951
## 0.35.3
1052

1153
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.35.3.

batch_logger.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ func (l *BatchLogger) run(ctx context.Context) {
125125
func (l *BatchLogger) processEvent(logs []Log) {
126126
event := NewEvent()
127127
event.Timestamp = time.Now()
128+
event.EventID = EventID(uuid())
128129
event.Type = logEvent.Type
129130
event.Logs = logs
130131
l.client.Transport.SendEvent(event)

client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const (
3232
// is of little use when debugging production errors with Sentry. The Sentry UI
3333
// is not optimized for long chains either. The top-level error together with a
3434
// stack trace is often the most useful information.
35-
maxErrorDepth = 10
35+
maxErrorDepth = 100
3636

3737
// defaultMaxSpans limits the default number of recorded spans per transaction. The limit is
3838
// meant to bound memory usage and prevent too large transaction events that

client_test.go

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -164,23 +164,27 @@ func TestCaptureException(t *testing.T) {
164164
err: pkgErrors.WithStack(&customErr{}),
165165
want: []Exception{
166166
{
167-
Type: "*sentry.customErr",
168-
Value: "wat",
167+
Type: "*sentry.customErr",
168+
Value: "wat",
169+
Stacktrace: nil,
169170
Mechanism: &Mechanism{
170-
Type: "generic",
171-
ExceptionID: 0,
172-
IsExceptionGroup: true,
171+
Type: MechanismTypeChained,
172+
ExceptionID: 1,
173+
ParentID: Pointer(0),
174+
Source: MechanismTypeUnwrap,
175+
IsExceptionGroup: false,
173176
},
174177
},
175178
{
176179
Type: "*errors.withStack",
177180
Value: "wat",
178181
Stacktrace: &Stacktrace{Frames: []Frame{}},
179182
Mechanism: &Mechanism{
180-
Type: "generic",
181-
ExceptionID: 1,
182-
ParentID: Pointer(0),
183-
IsExceptionGroup: true,
183+
Type: MechanismTypeGeneric,
184+
ExceptionID: 0,
185+
ParentID: nil,
186+
Source: "",
187+
IsExceptionGroup: false,
184188
},
185189
},
186190
},
@@ -201,23 +205,27 @@ func TestCaptureException(t *testing.T) {
201205
err: &customErrWithCause{cause: &customErr{}},
202206
want: []Exception{
203207
{
204-
Type: "*sentry.customErr",
205-
Value: "wat",
208+
Type: "*sentry.customErr",
209+
Value: "wat",
210+
Stacktrace: nil,
206211
Mechanism: &Mechanism{
207-
Type: "generic",
208-
ExceptionID: 0,
209-
IsExceptionGroup: true,
212+
Type: MechanismTypeChained,
213+
ExceptionID: 1,
214+
ParentID: Pointer(0),
215+
Source: "cause",
216+
IsExceptionGroup: false,
210217
},
211218
},
212219
{
213220
Type: "*sentry.customErrWithCause",
214221
Value: "err",
215222
Stacktrace: &Stacktrace{Frames: []Frame{}},
216223
Mechanism: &Mechanism{
217-
Type: "generic",
218-
ExceptionID: 1,
219-
ParentID: Pointer(0),
220-
IsExceptionGroup: true,
224+
Type: MechanismTypeGeneric,
225+
ExceptionID: 0,
226+
ParentID: nil,
227+
Source: "",
228+
IsExceptionGroup: false,
221229
},
222230
},
223231
},
@@ -227,23 +235,27 @@ func TestCaptureException(t *testing.T) {
227235
err: wrappedError{original: errors.New("original")},
228236
want: []Exception{
229237
{
230-
Type: "*errors.errorString",
231-
Value: "original",
238+
Type: "*errors.errorString",
239+
Value: "original",
240+
Stacktrace: nil,
232241
Mechanism: &Mechanism{
233-
Type: "generic",
234-
ExceptionID: 0,
235-
IsExceptionGroup: true,
242+
Type: MechanismTypeChained,
243+
ExceptionID: 1,
244+
ParentID: Pointer(0),
245+
Source: MechanismTypeUnwrap,
246+
IsExceptionGroup: false,
236247
},
237248
},
238249
{
239250
Type: "sentry.wrappedError",
240251
Value: "wrapped: original",
241252
Stacktrace: &Stacktrace{Frames: []Frame{}},
242253
Mechanism: &Mechanism{
243-
Type: "generic",
244-
ExceptionID: 1,
245-
ParentID: Pointer(0),
246-
IsExceptionGroup: true,
254+
Type: MechanismTypeGeneric,
255+
ExceptionID: 0,
256+
ParentID: nil,
257+
Source: "",
258+
IsExceptionGroup: false,
247259
},
248260
},
249261
},

echo/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.23
55
replace github.com/getsentry/sentry-go => ../
66

77
require (
8-
github.com/getsentry/sentry-go v0.35.3
8+
github.com/getsentry/sentry-go v0.36.1
99
github.com/google/go-cmp v0.5.9
1010
github.com/labstack/echo/v4 v4.10.0
1111
)

exception.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package sentry
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"slices"
7+
)
8+
9+
const (
10+
MechanismTypeGeneric string = "generic"
11+
MechanismTypeChained string = "chained"
12+
MechanismTypeUnwrap string = "unwrap"
13+
MechanismSourceCause string = "cause"
14+
)
15+
16+
type visited struct {
17+
comparable map[error]struct{}
18+
msgs map[string]struct{}
19+
}
20+
21+
func (v *visited) seenError(err error) bool {
22+
t := reflect.TypeOf(err)
23+
if t == nil {
24+
return false
25+
}
26+
27+
if t.Comparable() {
28+
if _, ok := v.comparable[err]; ok {
29+
return true
30+
}
31+
v.comparable[err] = struct{}{}
32+
return false
33+
}
34+
35+
key := t.String() + err.Error()
36+
if _, ok := v.msgs[key]; ok {
37+
return true
38+
}
39+
v.msgs[key] = struct{}{}
40+
return false
41+
}
42+
43+
func convertErrorToExceptions(err error, maxErrorDepth int) []Exception {
44+
var exceptions []Exception
45+
vis := &visited{
46+
make(map[error]struct{}),
47+
make(map[string]struct{}),
48+
}
49+
convertErrorDFS(err, &exceptions, nil, "", vis, maxErrorDepth, 0)
50+
51+
// mechanism type is used for debugging purposes, but since we can't really distinguish the origin of who invoked
52+
// captureException, we set it to nil if the error is not chained.
53+
if len(exceptions) == 1 {
54+
exceptions[0].Mechanism = nil
55+
}
56+
57+
slices.Reverse(exceptions)
58+
59+
// Add a trace of the current stack to the top level(outermost) error in a chain if
60+
// it doesn't have a stack trace yet.
61+
// We only add to the most recent error to avoid duplication and because the
62+
// current stack is most likely unrelated to errors deeper in the chain.
63+
if len(exceptions) > 0 && exceptions[len(exceptions)-1].Stacktrace == nil {
64+
exceptions[len(exceptions)-1].Stacktrace = NewStacktrace()
65+
}
66+
67+
return exceptions
68+
}
69+
70+
func convertErrorDFS(err error, exceptions *[]Exception, parentID *int, source string, visited *visited, maxErrorDepth int, currentDepth int) {
71+
if err == nil {
72+
return
73+
}
74+
75+
if visited.seenError(err) {
76+
return
77+
}
78+
79+
_, isExceptionGroup := err.(interface{ Unwrap() []error })
80+
81+
exception := Exception{
82+
Value: err.Error(),
83+
Type: reflect.TypeOf(err).String(),
84+
Stacktrace: ExtractStacktrace(err),
85+
}
86+
87+
currentID := len(*exceptions)
88+
89+
var mechanismType string
90+
91+
if parentID == nil {
92+
mechanismType = MechanismTypeGeneric
93+
source = ""
94+
} else {
95+
mechanismType = MechanismTypeChained
96+
}
97+
98+
exception.Mechanism = &Mechanism{
99+
Type: mechanismType,
100+
ExceptionID: currentID,
101+
ParentID: parentID,
102+
Source: source,
103+
IsExceptionGroup: isExceptionGroup,
104+
}
105+
106+
*exceptions = append(*exceptions, exception)
107+
108+
if maxErrorDepth >= 0 && currentDepth >= maxErrorDepth {
109+
return
110+
}
111+
112+
switch v := err.(type) {
113+
case interface{ Unwrap() []error }:
114+
unwrapped := v.Unwrap()
115+
for i := range unwrapped {
116+
if unwrapped[i] != nil {
117+
childSource := fmt.Sprintf("errors[%d]", i)
118+
convertErrorDFS(unwrapped[i], exceptions, &currentID, childSource, visited, maxErrorDepth, currentDepth+1)
119+
}
120+
}
121+
case interface{ Unwrap() error }:
122+
unwrapped := v.Unwrap()
123+
if unwrapped != nil {
124+
convertErrorDFS(unwrapped, exceptions, &currentID, MechanismTypeUnwrap, visited, maxErrorDepth, currentDepth+1)
125+
}
126+
case interface{ Cause() error }:
127+
cause := v.Cause()
128+
if cause != nil {
129+
convertErrorDFS(cause, exceptions, &currentID, MechanismSourceCause, visited, maxErrorDepth, currentDepth+1)
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)