From 6c47ec917bc4644f77ddbd2ef364789623bb02a0 Mon Sep 17 00:00:00 2001 From: Zen Quah Date: Tue, 27 May 2025 16:59:26 +0800 Subject: [PATCH 1/5] Enhance Sentry error capturing with optional metadata support --- errors/sentry.go | 81 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/errors/sentry.go b/errors/sentry.go index 7d52d3b..b4604b2 100644 --- a/errors/sentry.go +++ b/errors/sentry.go @@ -5,21 +5,39 @@ import ( "encoding/json" "fmt" + "maps" + "github.com/getsentry/sentry-go" "github.com/wego/pkg/env" ) -// CaptureError captures an error to sentry & set level as error -func CaptureError(ctx context.Context, err error) { - capture(ctx, err, sentry.LevelError) +// ErrorData carries optional extra data to attach to the error event +type ErrorData struct { + Extra map[string]any + Tags map[string]string +} + +// CaptureError captures an error to sentry & set level as error. +// It is backward compatible: info is an optional parameter. +func CaptureError(ctx context.Context, err error, info ...*ErrorData) { + var ei *ErrorData + if len(info) > 0 { + ei = info[0] + } + capture(ctx, err, sentry.LevelError, ei) } -// CaptureWarning captures an error to sentry & set level as warning -func CaptureWarning(ctx context.Context, err error) { - capture(ctx, err, sentry.LevelWarning) +// CaptureWarning captures an error to sentry & set level as warning. +// It is backward compatible: info is an optional parameter. +func CaptureWarning(ctx context.Context, err error, info ...*ErrorData) { + var ei *ErrorData + if len(info) > 0 { + ei = info[0] + } + capture(ctx, err, sentry.LevelWarning, ei) } -func capture(ctx context.Context, err error, level sentry.Level) { +func capture(ctx context.Context, err error, level sentry.Level, info *ErrorData) { if !env.IsProduction() && !env.IsStaging() { return } @@ -27,43 +45,60 @@ func capture(ctx context.Context, err error, level sentry.Level) { hub := getHub(ctx) hub.WithScope(func(scope *sentry.Scope) { scope.SetLevel(level) - enrichScope(ctx, scope, err) + enrichScope(ctx, scope, err, info) hub.CaptureException(err) }) } -func enrichScope(ctx context.Context, scope *sentry.Scope, err error) { +func enrichScope(ctx context.Context, scope *sentry.Scope, err error, info *ErrorData) { + // Prepare tags and extras to set + var tagsToSet map[string]string = make(map[string]string) + var extrasToSet map[string]any = make(map[string]any) + + // Fetch error code errorCode := fmt.Sprint(Code(err)) - scope.SetTag(SentryErrorCode, errorCode) - fingerprint := []string{errorCode, err.Error()} + tagsToSet[SentryErrorCode] = errorCode + // Fingeprinting is handed over to the SDK ({{default}} is the default fingerprint), with the additional error code field to add a dimension of uniqueness + fingerprint := []string{"{{default}}", errorCode} + + // If the error is an Error type, we can enrich the scope with the basics and extras e, ok := err.(*Error) if ok { + // For each basic key-value pair, set it as a tag for key, value := range e.basics() { if tag, err := json.Marshal(value); err == nil { - scope.SetTag(key, string(tag)) - fingerprint = append(fingerprint, key) + tagsToSet[key] = string(tag) } } - // extra is not searchable in sentry + // For each operation, set it as an extra + // Note: extra is not searchable in Sentry ops := ops(e) - scope.SetExtra(SentryOperations, ops) - for _, o := range ops { - fingerprint = append(fingerprint, string(o)) - } + extrasToSet[SentryOperations] = ops - for k, v := range e.extras() { - scope.SetExtra(k, v) - } + // Merge e.extras() into extrasToSet + maps.Copy(extrasToSet, e.extras()) + } + + // If any info is provided, add them to the event scope + if info != nil { + // Merge info.Extra into extrasToSet + maps.Copy(extrasToSet, info.Extra) + + // Merge info.Tags into tagsToSet + maps.Copy(tagsToSet, info.Tags) } + // Get the request ID from the context reqID, ok := ctx.Value(SentryRequestID).(string) if ok { - scope.SetTag(SentryRequestID, reqID) - fingerprint = append(fingerprint, reqID) + tagsToSet[SentryRequestID] = reqID } + // Finally, update the scope with the tags, extras and fingerprint + scope.SetTags(tagsToSet) + scope.SetExtras(extrasToSet) scope.SetFingerprint(fingerprint) } From 704034d450190af45aed8cfaa43226fdf36b0d7b Mon Sep 17 00:00:00 2001 From: Zen Quah Date: Tue, 27 May 2025 17:00:49 +0800 Subject: [PATCH 2/5] Fix var revive issue --- errors/sentry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/errors/sentry.go b/errors/sentry.go index b4604b2..39535c2 100644 --- a/errors/sentry.go +++ b/errors/sentry.go @@ -52,8 +52,8 @@ func capture(ctx context.Context, err error, level sentry.Level, info *ErrorData func enrichScope(ctx context.Context, scope *sentry.Scope, err error, info *ErrorData) { // Prepare tags and extras to set - var tagsToSet map[string]string = make(map[string]string) - var extrasToSet map[string]any = make(map[string]any) + var tagsToSet = make(map[string]string) + var extrasToSet = make(map[string]any) // Fetch error code errorCode := fmt.Sprint(Code(err)) From 5b16e04364ab74ae21acabb762efc5b421a843ae Mon Sep 17 00:00:00 2001 From: Zen Quah Date: Thu, 20 Nov 2025 12:07:37 +0800 Subject: [PATCH 3/5] Remove ErrorData feature --- errors/sentry.go | 43 +++++++++---------------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/errors/sentry.go b/errors/sentry.go index 39535c2..21a542c 100644 --- a/errors/sentry.go +++ b/errors/sentry.go @@ -11,33 +11,17 @@ import ( "github.com/wego/pkg/env" ) -// ErrorData carries optional extra data to attach to the error event -type ErrorData struct { - Extra map[string]any - Tags map[string]string +// CaptureError captures an error to sentry & set level as error +func CaptureError(ctx context.Context, err error) { + capture(ctx, err, sentry.LevelError) } -// CaptureError captures an error to sentry & set level as error. -// It is backward compatible: info is an optional parameter. -func CaptureError(ctx context.Context, err error, info ...*ErrorData) { - var ei *ErrorData - if len(info) > 0 { - ei = info[0] - } - capture(ctx, err, sentry.LevelError, ei) -} - -// CaptureWarning captures an error to sentry & set level as warning. -// It is backward compatible: info is an optional parameter. -func CaptureWarning(ctx context.Context, err error, info ...*ErrorData) { - var ei *ErrorData - if len(info) > 0 { - ei = info[0] - } - capture(ctx, err, sentry.LevelWarning, ei) +// CaptureWarning captures an error to sentry & set level as warning +func CaptureWarning(ctx context.Context, err error) { + capture(ctx, err, sentry.LevelWarning) } -func capture(ctx context.Context, err error, level sentry.Level, info *ErrorData) { +func capture(ctx context.Context, err error, level sentry.Level) { if !env.IsProduction() && !env.IsStaging() { return } @@ -45,12 +29,12 @@ func capture(ctx context.Context, err error, level sentry.Level, info *ErrorData hub := getHub(ctx) hub.WithScope(func(scope *sentry.Scope) { scope.SetLevel(level) - enrichScope(ctx, scope, err, info) + enrichScope(ctx, scope, err) hub.CaptureException(err) }) } -func enrichScope(ctx context.Context, scope *sentry.Scope, err error, info *ErrorData) { +func enrichScope(ctx context.Context, scope *sentry.Scope, err error) { // Prepare tags and extras to set var tagsToSet = make(map[string]string) var extrasToSet = make(map[string]any) @@ -81,15 +65,6 @@ func enrichScope(ctx context.Context, scope *sentry.Scope, err error, info *Erro maps.Copy(extrasToSet, e.extras()) } - // If any info is provided, add them to the event scope - if info != nil { - // Merge info.Extra into extrasToSet - maps.Copy(extrasToSet, info.Extra) - - // Merge info.Tags into tagsToSet - maps.Copy(tagsToSet, info.Tags) - } - // Get the request ID from the context reqID, ok := ctx.Value(SentryRequestID).(string) if ok { From a42bdc6a8736b9ca01eef325d1051564c97811d0 Mon Sep 17 00:00:00 2001 From: Zen Quah Date: Thu, 20 Nov 2025 12:13:41 +0800 Subject: [PATCH 4/5] Disable Revive package name checks due to `errors` package conflicting with Go standard library --- revive.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/revive.toml b/revive.toml index c8aa990..ff82c0d 100644 --- a/revive.toml +++ b/revive.toml @@ -30,6 +30,7 @@ warningCode = 1 [rule.if-return] [rule.increment-decrement] [rule.var-naming] +arguments = [[], [], [{ skipPackageNameChecks = true }]] # [rule.package-comments] [rule.range] [rule.receiver-naming] From b9e25e94b3049f729e242378b957af24b1ebe9ab Mon Sep 17 00:00:00 2001 From: Zen Quah Date: Fri, 21 Nov 2025 10:51:36 +0800 Subject: [PATCH 5/5] Improve comments --- errors/sentry.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/errors/sentry.go b/errors/sentry.go index 21a542c..88bc876 100644 --- a/errors/sentry.go +++ b/errors/sentry.go @@ -43,7 +43,7 @@ func enrichScope(ctx context.Context, scope *sentry.Scope, err error) { errorCode := fmt.Sprint(Code(err)) tagsToSet[SentryErrorCode] = errorCode - // Fingeprinting is handed over to the SDK ({{default}} is the default fingerprint), with the additional error code field to add a dimension of uniqueness + // Fingerprinting is handed over to the SDK ({{default}} is the default fingerprint), with the additional error code field to add a dimension of uniqueness fingerprint := []string{"{{default}}", errorCode} // If the error is an Error type, we can enrich the scope with the basics and extras @@ -62,6 +62,7 @@ func enrichScope(ctx context.Context, scope *sentry.Scope, err error) { extrasToSet[SentryOperations] = ops // Merge e.extras() into extrasToSet + // Note: maps.Copy overwrites existing keys in the destination map. maps.Copy(extrasToSet, e.extras()) }