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.

Copied
go get github.com/getsentry/sentry-go
go get github.com/getsentry/sentry-go/grpc

Copied
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.

Copied
// 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:

Copied
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-trace and baggage metadata.
  • Attach an isolated *sentry.Hub to the handler's context.

Attach the unary and stream interceptors when creating your gRPC client:

Copied
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-trace and baggage headers 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.

Copied
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
Was this helpful?
Help improve this content
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").