A friendly Go library that seamlessly integrates Sentry error tracking with Temporal workflows and activities.
At Uphold, we rely heavily on Temporal for our critical business workflows. When things go wrong (and they sometimes do!), we needed a clean way to capture errors and panics in Sentry for better observability and debugging. After building this solution for our internal projects, we realized the Go ecosystem was missing a package that really nailed this integration, so we decided to open source this!
go get github.com/uphold/temporal-sentry-interceptor
package main
import (
"log"
"github.com/getsentry/sentry-go"
temporalsentry "github.com/uphold/temporal-sentry-interceptor"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/interceptor"
"go.temporal.io/sdk/worker"
)
func main() {
// Initialize Sentry.
err := sentry.Init(sentry.ClientOptions{
Dsn: "your-sentry-dsn",
})
if err != nil {
log.Fatalf("Failed to initialize Sentry: %v", err)
}
// Create Temporal client.
c, err := client.Dial(client.Options{
/* client options */
})
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()
// Create worker with Sentry interceptor.
w := worker.New(c, "your-task-queue", worker.Options{
Interceptors: []interceptor.WorkerInterceptor{
temporalsentry.New(),
},
})
// Register your workflows and activities.
w.RegisterWorkflow(YourWorkflow)
w.RegisterActivity(YourActivity)
// Start the worker.
err = w.Run(worker.InterruptCh())
if err != nil {
log.Fatalln("Unable to start worker", err)
}
}
Configure how Sentry tags are set:
interceptor := temporalsentry.New(
temporalsentry.WithConfigureSentryScope(func(
ctx context.Context,
request []any,
activityInfo *activity.Info,
workflowInfo *workflow.Info,
) func(scope *sentry.Scope) {
return func(scope *sentry.Scope) {
if workflowInfo != nil {
scope.SetTag("workflow_type", workflowInfo.WorkflowType.Name)
scope.SetTag("workflow_id", workflowInfo.WorkflowExecution.ID)
}
if activityInfo != nil {
scope.SetTag("activity_type", activityInfo.ActivityType.Name)
}
}
}),
)
Control which errors and panics get reported:
interceptor := temporalsentry.New(
// Filter workflow errors.
temporalsentry.WithFilterWorkflowError(func(err error, request []any, info *workflow.Info) bool {
// Return true to skip reporting this error.
return errors.Is(err, temporal.ErrCanceled)
}),
// Filter workflow panics.
temporalsentry.WithFilterWorkflowPanic(func(p any, request []any, info *workflow.Info) bool {
// Return true to skip reporting this panic.
if panicMsg, ok := p.(string); ok {
return strings.Contains(panicMsg, "expected panic")
}
return false
}),
)
Configure the activity options for workflow errors and panics:
Important note: Local activities should not take more than the Temporal workflow task timeout (which defaults to 10 seconds), be mindful of that if changing the timeout from its default of 5 seconds. See the difference between Temporal activities and local activities for more information.
interceptor := temporalsentry.New(
temporalsentry.WithWorkflowErrorActivityOptions(workflow.LocalActivityOptions{
ScheduleToCloseTimeout: 10 * time.Second,
RetryPolicy: &temporal.RetryPolicy{
MaximumAttempts: 3,
},
}),
)
You can easily chain multiple interceptors:
Important note: A general rule of thumb is that the Sentry interceptor should go last, as it ensures that it wraps the entire call stack and that other interceptors that might handle errors or recover from panics don't erase them.
w := worker.New(c, "task-queue", worker.Options{
Interceptors: []interceptor.WorkerInterceptor{
yourCustomInterceptor.New(),
temporalsentry.New(/* your options */), // important to place the Sentry interceptor last!
},
})
It is recommended that Sentry's HTTPSyncTransport
is not used, as all calls to sentry.CaptureException
and sentry.Recover
will block on the request being captured to Sentry if that transport is used. If the application's network connection to Sentry's servers is unreliable or unavailable it will cause issues due to the constraints that Temporal's local activities have of not being able to run for more than the amount of time a workflow task can (default is 10 seconds).
We welcome contributions! Whether it's bug reports, feature requests, or pull requests!
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push fork feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Issues: Found a bug? Open an issue
- Feature Requests: Have an idea? We'd love to hear it!
Made with ❤️ by the team at Uphold