Refactor Sentry error reporting pattern with flexible options across application layers#62
Conversation
- Added `SentryReportOptions` struct to allow passing extra context, custom tags, and severity level. - Introduced `ReportErrorWithSentryOptions` method for reporting with fine-grained control. - Improved `ConfigureScope` by including `goarch` and renaming `version` tag to `app_version`. - Renamed `ReportIfProd` to `ReportError` and removed environment check to allow caller control.
…pplication flow - Add Reporter dependency to application struct and main functions - Report errors to Sentry with tags and context across GTFS download, config loading, and cache directory creation - Refactor main.go to pass Reporter to functions that can produce reportable errors
There was a problem hiding this comment.
Pull Request Overview
This PR refactors how Sentry error reporting is handled by introducing a unified options struct and updating all layers to use a consistent reporting helper.
- Introduces
SentryReportOptionsandReportErrorWithSentryOptions - Renames
ReportIfProd→ReportErrorand standardizes Sentry scope tags (e.g.,app_version,goarch) - Injects
Reporterinto main, metrics, and test code and updates function signatures accordingly
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/report/reporter.go | Added SentryReportOptions type, ReportErrorWithSentryOptions, updated ReportError and scope configuration |
| cmd/watchdog/metrics.go | Replaced ad-hoc Sentry calls with ReportErrorWithSentryOptions in collectMetricsForServer |
| cmd/watchdog/main_test.go | Injected Reporter into tests and updated calls to loader functions |
| cmd/watchdog/main.go | Wired reporter through application, updated function signatures, set up and flushed Sentry |
Comments suppressed due to low confidence (2)
internal/report/reporter.go:7
- This file uses
runtime.Version(),runtime.GOARCH, andos.Hostname(), but neitherruntimenorosare imported. Add"runtime"and"os"to the import block to avoid compile errors.
import (
internal/report/reporter.go:70
- Consider adding unit tests for
ReportErrorandReportErrorWithSentryOptionsto verify that tags, context, and default level behaviors are applied correctly.
type SentryReportOptions struct {
| if opts.Level != "" { | ||
| scope.SetLevel(opts.Level) | ||
| } |
There was a problem hiding this comment.
[nitpick] If no level is provided (opts.Level is zero), no level is set on the scope. Consider defaulting opts.Level to sentry.LevelError before this check so every reported error has an explicit severity.
| if opts.Level != "" { | |
| scope.SetLevel(opts.Level) | |
| } | |
| level := opts.Level | |
| if level == "" { | |
| level = sentry.LevelError | |
| } | |
| scope.SetLevel(level) |
| tmpFile.Close() | ||
|
|
||
| servers, err := loadConfigFromFile(tmpFile.Name()) | ||
| servers, err := loadConfigFromFile(tmpFile.Name() , reporter) |
There was a problem hiding this comment.
[nitpick] There’s a stray space before the comma in the function call. Removing it (tmpFile.Name(), reporter) will keep the code consistent with standard Go formatting.
| servers, err := loadConfigFromFile(tmpFile.Name() , reporter) | |
| servers, err := loadConfigFromFile(tmpFile.Name(), reporter) |
aaronbrethorst
left a comment
There was a problem hiding this comment.
code looks good! I have some nitpicks I'd like to see addressed before merging, but this is good work! 👍
|
|
||
| cacheDir := "cache" | ||
| if err = createCacheDirectory(cacheDir, logger); err != nil { | ||
| if err = createCacheDirectory(cacheDir, logger , reporter); err != nil { |
There was a problem hiding this comment.
there's an extra space in between logger and the comma. it should look like this: logger, reporter
|
|
||
| // Download GTFS bundles for all servers on startup | ||
| downloadGTFSBundles(servers, cacheDir, logger) | ||
| downloadGTFSBundles(servers, cacheDir, logger , reporter) |
There was a problem hiding this comment.
there's an extra space in between logger and the comma. it should look like this: logger, reporter.
|
|
||
| // Cron job to download GTFS bundles for all servers every 24 hours | ||
| go refreshGTFSBundles(servers, cacheDir, logger , 24 * time.Hour) | ||
| go refreshGTFSBundles(servers, cacheDir, logger , 24 * time.Hour , reporter) |
There was a problem hiding this comment.
there's an extra space in between logger and the comma. it should look like this: logger, reporter
| if err != nil { | ||
| if os.IsNotExist(err){ | ||
| if err := os.MkdirAll(cacheDir, os.ModePerm); err != nil { | ||
| reporter.ReportErrorWithSentryOptions(err, report.SentryReportOptions{ |
There was a problem hiding this comment.
I think you need to run go fmt on this code. the indentation is strange.
| } | ||
| if !stat.IsDir() { | ||
| return fmt.Errorf("%s is not a directory", cacheDir) | ||
| err := fmt.Errorf("%s is not a directory", cacheDir) |
There was a problem hiding this comment.
same here on formatting.
| _, err := utils.DownloadGTFSBundle(server.GtfsUrl, cacheDir, server.ID, hashStr) | ||
| if err != nil { | ||
| reporter.ReportErrorWithSentryOptions(err, report.SentryReportOptions{ | ||
| Tags: map[string]string{ |
|
|
||
| // refreshGTFSBundles periodically downloads GTFS bundles at the specified interval. | ||
| func refreshGTFSBundles(servers []models.ObaServer, cacheDir string, logger *slog.Logger , interval time.Duration) { | ||
| func refreshGTFSBundles(servers []models.ObaServer, cacheDir string, logger *slog.Logger , interval time.Duration , reporter *report.Reporter) { |
There was a problem hiding this comment.
watch those extra spaces again ,, here and throughout the function below
| newServers, err := loadConfigFromURL(configURL, configAuthUser, configAuthPass , reporter) | ||
| if err != nil { | ||
| reporter.ReportErrorWithSentryOptions(err, report.SentryReportOptions{ | ||
| Tags: map[string]string{ |
There was a problem hiding this comment.
you're creating these map[string]string types a lot here for maps with a single key/value pair. I recommend creating a helper function that can generate them for you. it'll make the code more readable:
func makeMap(key, value string) map[string]string {
return map[string]string{ key: value }
}
| func loadConfigFromFile(filePath string , reporter *report.Reporter) ([]models.ObaServer, error) { | ||
| data, err := os.ReadFile(filePath) | ||
| if err != nil { | ||
| reporter.ReportErrorWithSentryOptions(err, report.SentryReportOptions{ |
|
|
||
| var servers []models.ObaServer | ||
| if err := json.Unmarshal(data, &servers); err != nil { | ||
| reporter.ReportErrorWithSentryOptions(err, report.SentryReportOptions{ |
SentryReportOptionsfor passing tags, context, and severity levelReportErrorWithSentryOptionsto replace ad-hoc reporting calls with a consistent helperConfigureScopeto includegoarchand renamedversiontag toapp_versionReportIfProd→ReportErrorReporterwith the new pattern (GTFS download, config loading, cache creation)collectMetricsForServermain_test.gofor Reporter integration