gRPC
Learn how to add Sentry to a Go gRPC server or client using interceptors.
For a quick reference, there is a complete example at the Go SDK source code repository.
Go Dev-style API documentation is also available.
go get github.com/getsentry/sentry-go
go get github.com/getsentry/sentry-go/grpc
go get github.com/getsentry/sentry-go
go get github.com/getsentry/sentry-go/grpc
err := sentry.Init(sentry.ClientOptions{
Dsn: "___PUBLIC_DSN___",
// Enable printing of SDK debug messages.
// Useful when getting started or trying to figure something out.
Debug: true,
// Adds request headers and IP for users,
// visit: https://docs.sentry.io/platforms/go/data-management/data-collected/ for more info
SendDefaultPII: true,
// ___PRODUCT_OPTION_START___ performance
EnableTracing: true,
// Set TracesSampleRate to 1.0 to capture 100%
// of transactions for tracing.
TracesSampleRate: 1.0,
// ___PRODUCT_OPTION_END___ performance
// ___PRODUCT_OPTION_START___ logs
// Logs are enabled by default. To disable them, set DisableLogs: true.
// ___PRODUCT_OPTION_END___ logs
})
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)
err := sentry.Init(sentry.ClientOptions{
Dsn: "___PUBLIC_DSN___",
// Enable printing of SDK debug messages.
// Useful when getting started or trying to figure something out.
Debug: true,
// Adds request headers and IP for users,
// visit: https://docs.sentry.io/platforms/go/data-management/data-collected/ for more info
SendDefaultPII: true,
// ___PRODUCT_OPTION_START___ performance
EnableTracing: true,
// Set TracesSampleRate to 1.0 to capture 100%
// of transactions for tracing.
TracesSampleRate: 1.0,
// ___PRODUCT_OPTION_END___ performance
// ___PRODUCT_OPTION_START___ logs
// Logs are enabled by default. To disable them, set DisableLogs: true.
// ___PRODUCT_OPTION_END___ logs
})
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)
sentrygrpc accepts a struct of ServerOptions that allows you to configure how the server interceptors behave.
// Whether Sentry should repanic after recovery. In most cases it should be set to true,
// so that your own recovery middleware or gRPC's default handling can respond to the client.
Repanic bool
// Whether to wait for Sentry to deliver the event before returning.
// Useful when Repanic is true and the process may exit or restart after the panic.
WaitForDelivery bool
// Timeout for the event delivery requests.
Timeout time.Duration
// Whether Sentry should repanic after recovery. In most cases it should be set to true,
// so that your own recovery middleware or gRPC's default handling can respond to the client.
Repanic bool
// Whether to wait for Sentry to deliver the event before returning.
// Useful when Repanic is true and the process may exit or restart after the panic.
WaitForDelivery bool
// Timeout for the event delivery requests.
Timeout time.Duration
Attach the unary and stream interceptors when creating your gRPC server:
import (
"fmt"
"net"
"google.golang.org/grpc"
"github.com/getsentry/sentry-go"
sentrygrpc "github.com/getsentry/sentry-go/grpc"
)
func main() {
if err := sentry.Init(sentry.ClientOptions{
Dsn: "___PUBLIC_DSN___",
TracesSampleRate: 1.0,
}); err != nil {
fmt.Printf("Sentry initialization failed: %v\n", err)
}
defer sentry.Flush(2 * time.Second)
server := grpc.NewServer(
grpc.UnaryInterceptor(sentrygrpc.UnaryServerInterceptor(sentrygrpc.ServerOptions{
Repanic: true,
})),
grpc.StreamInterceptor(sentrygrpc.StreamServerInterceptor(sentrygrpc.ServerOptions{
Repanic: true,
})),
)
listener, err := net.Listen("tcp", ":50051")
if err != nil {
sentry.CaptureException(err)
return
}
if err := server.Serve(listener); err != nil {
sentry.CaptureException(err)
}
}
import (
"fmt"
"net"
"google.golang.org/grpc"
"github.com/getsentry/sentry-go"
sentrygrpc "github.com/getsentry/sentry-go/grpc"
)
func main() {
if err := sentry.Init(sentry.ClientOptions{
Dsn: "___PUBLIC_DSN___",
TracesSampleRate: 1.0,
}); err != nil {
fmt.Printf("Sentry initialization failed: %v\n", err)
}
defer sentry.Flush(2 * time.Second)
server := grpc.NewServer(
grpc.UnaryInterceptor(sentrygrpc.UnaryServerInterceptor(sentrygrpc.ServerOptions{
Repanic: true,
})),
grpc.StreamInterceptor(sentrygrpc.StreamServerInterceptor(sentrygrpc.ServerOptions{
Repanic: true,
})),
)
listener, err := net.Listen("tcp", ":50051")
if err != nil {
sentry.CaptureException(err)
return
}
if err := server.Serve(listener); err != nil {
sentry.CaptureException(err)
}
}
The server interceptors automatically:
- Create a transaction for each unary or streaming RPC call.
- Recover from panics in handlers and report them to Sentry.
- Continue distributed traces from upstream clients via
sentry-traceandbaggagemetadata. - Attach an isolated
*sentry.Hubto the handler's context.
Attach the unary and stream interceptors when creating your gRPC client:
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/getsentry/sentry-go"
sentrygrpc "github.com/getsentry/sentry-go/grpc"
)
func main() {
if err := sentry.Init(sentry.ClientOptions{
Dsn: "___PUBLIC_DSN___",
TracesSampleRate: 1.0,
}); err != nil {
fmt.Printf("Sentry initialization failed: %v\n", err)
}
defer sentry.Flush(2 * time.Second)
conn, err := grpc.NewClient(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(sentrygrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(sentrygrpc.StreamClientInterceptor()),
)
if err != nil {
sentry.CaptureException(err)
return
}
defer conn.Close()
}
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/getsentry/sentry-go"
sentrygrpc "github.com/getsentry/sentry-go/grpc"
)
func main() {
if err := sentry.Init(sentry.ClientOptions{
Dsn: "___PUBLIC_DSN___",
TracesSampleRate: 1.0,
}); err != nil {
fmt.Printf("Sentry initialization failed: %v\n", err)
}
defer sentry.Flush(2 * time.Second)
conn, err := grpc.NewClient(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(sentrygrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(sentrygrpc.StreamClientInterceptor()),
)
if err != nil {
sentry.CaptureException(err)
return
}
defer conn.Close()
}
The client interceptors automatically:
- Create a child span for each outgoing RPC call.
- Inject
sentry-traceandbaggageheaders into gRPC metadata for distributed tracing. - Set the span status based on the returned gRPC status code.
Both interceptors make a *sentry.Hub available on the request context, which you can retrieve using sentry.GetHubFromContext() in your handlers. Use this hub-bound API instead of the global sentry.CaptureMessage or sentry.CaptureException calls to keep data separated between concurrent requests.
func (s *server) YourMethod(ctx context.Context, req *pb.YourRequest) (*pb.YourResponse, error) {
if hub := sentry.GetHubFromContext(ctx); hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetTag("request_id", req.GetId())
hub.CaptureMessage("Handling request")
})
}
return &pb.YourResponse{}, nil
}
func (s *server) YourMethod(ctx context.Context, req *pb.YourRequest) (*pb.YourResponse, error) {
if hub := sentry.GetHubFromContext(ctx); hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetTag("request_id", req.GetId())
hub.CaptureMessage("Handling request")
})
}
return &pb.YourResponse{}, nil
}
- Explore practical guides on what to monitor, log, track, and investigate after setup
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").