gRPC Integration

Learn more about the Sentry gRPC integration for the Dart SDK.

The sentry_grpc package provides gRPC support for Sentry. It instruments outgoing unary calls with spans, records breadcrumbs, injects distributed tracing headers into gRPC metadata, and can capture failed calls as Sentry errors.

Add sentry_grpc to your dependencies:

pubspec.yaml
Copied
dependencies:
  sentry: ^9.29.0
  sentry_grpc: ^9.29.0
  grpc: '>=4.0.4 <6.0.0'

Add SentryGrpcInterceptor to your gRPC client's interceptor list. This should happen once, when the client is created.

Copied
import 'package:grpc/grpc.dart';
import 'package:sentry/sentry.dart';
import 'package:sentry_grpc/sentry_grpc.dart';

Future<void> main() async {
  await Sentry.init(
    (options) {
      options.dsn = '___PUBLIC_DSN___';
      options.tracesSampleRate = 1.0;
    },
    appRunner: runApp,
  );
}

Future<void> runApp() async {
  final channel = ClientChannel(
    'api.example.com',
    options: const ChannelOptions(credentials: ChannelCredentials.secure()),
  );

  final client = MyServiceClient(
    channel,
    interceptors: [SentryGrpcInterceptor()],
  );

  // All calls through this client are now automatically instrumented.
}

When a gRPC call fails, either because the server returns a non-OK status code or because a transport error occurs, the interceptor can capture it as a Sentry error.

This feature is controlled by captureFailedRequests. By default, the interceptor respects the global SentryOptions.captureFailedRequests setting. You can override this per-interceptor:

Copied
// Capture failed gRPC calls regardless of the global setting
final interceptor = SentryGrpcInterceptor(captureFailedRequests: true);

// Disable for this client only
final interceptor = SentryGrpcInterceptor(captureFailedRequests: false);

When a failed call is captured, Sentry attaches the gRPC method path, status code, status name, and error message as context on the event.

The interceptor automatically records a breadcrumb for every unary call. Successful calls are recorded at the info level; failed calls at the error level. Each breadcrumb includes the method path, gRPC status code and name, and call duration in milliseconds.

To disable breadcrumbs:

Copied
final interceptor = SentryGrpcInterceptor(enableBreadcrumbs: false);

For each unary call, the interceptor:

  • Creates a child span under the active transaction with operation grpc.client and a description set to the full method path (for example, /helloworld.Greeter/SayHello).
  • Sets span data following OpenTelemetry gRPC semantic conventions:
    • rpc.system = grpc
    • rpc.service = the service portion of the method path (for example, helloworld.Greeter)
    • rpc.method = the method name (for example, SayHello)
    • rpc.response.status_code = the gRPC status name (for example, OK, NOT_FOUND)
  • Injects sentry-trace and baggage headers into the call's gRPC metadata for distributed tracing.
  • Finishes the span and sets its status when the call completes.
  • Associates any thrown exception with the span.

By default, sentry-trace and baggage headers are injected for all gRPC method paths. Use tracePropagationTargets to restrict injection to specific services or methods. Targets are matched against the full method path (for example, /helloworld.Greeter/SayHello):

Copied
await Sentry.init((options) {
  options.dsn = '___PUBLIC_DSN___';
  options.tracePropagationTargets = [
    'myservice.MyService', // Matches any method under this service
  ];
});

When sendDefaultPii is enabled, the interceptor attaches the gRPC call's metadata headers to the span as rpc.request.metadata.<key> attributes. This is opt-in because metadata can contain sensitive values such as authorization tokens.

Copied
await Sentry.init((options) {
  options.dsn = '___PUBLIC_DSN___';
  options.sendDefaultPii = true; // Metadata headers will appear in span data
});

Before enabling tracing:

  1. Initialize the Sentry SDK. Learn more here.
  2. Set up tracing. Learn more here.

Copied
import 'package:grpc/grpc.dart';
import 'package:sentry/sentry.dart';
import 'package:sentry_grpc/sentry_grpc.dart';

Future<void> makeGrpcRequest() async {
  final channel = ClientChannel('api.example.com');
  final client = GreeterClient(
    channel,
    interceptors: [SentryGrpcInterceptor()],
  );

  // Start a transaction so the interceptor has a parent span to attach to.
  final transaction = Sentry.startTransaction(
    'grpc-example',
    'grpc.client',
    bindToScope: true,
  );

  try {
    final response = await client.sayHello(HelloRequest(name: 'Sentry'));
    transaction.status = const SpanStatus.ok();
    print(response.message);
  } catch (exception, stackTrace) {
    transaction.throwable = exception;
    transaction.status = const SpanStatus.internalError();
    await Sentry.captureException(exception, stackTrace: stackTrace);
  } finally {
    await transaction.finish();
  }
}

To view the recorded transaction, log into sentry.io and open your project. Clicking Performance will open a page with transactions, where you can select the transaction with the name grpc-example and see the grpc.client child span for the /helloworld.Greeter/SayHello call.

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").