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.yamldependencies:
sentry: ^9.29.0
sentry_grpc: ^9.29.0
grpc: '>=4.0.4 <6.0.0'
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.
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.
}
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:
// Capture failed gRPC calls regardless of the global setting
final interceptor = SentryGrpcInterceptor(captureFailedRequests: true);
// Disable for this client only
final interceptor = SentryGrpcInterceptor(captureFailedRequests: false);
// 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:
final interceptor = SentryGrpcInterceptor(enableBreadcrumbs: false);
final interceptor = SentryGrpcInterceptor(enableBreadcrumbs: false);
For each unary call, the interceptor:
- Creates a child span under the active transaction with operation
grpc.clientand a description set to the full method path (for example,/helloworld.Greeter/SayHello). - Sets span data following OpenTelemetry gRPC semantic conventions:
rpc.system=grpcrpc.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-traceandbaggageheaders 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.
Spans are only created when there is an active transaction on the scope. If no transaction is active, the interceptor still injects trace headers and records breadcrumbs.
Server-streaming and client-streaming calls receive distributed tracing header injection only. Full span lifecycle tracking for streaming RPCs will be added in a future release.
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):
await Sentry.init((options) {
options.dsn = '___PUBLIC_DSN___';
options.tracePropagationTargets = [
'myservice.MyService', // Matches any method under this service
];
});
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.
await Sentry.init((options) {
options.dsn = '___PUBLIC_DSN___';
options.sendDefaultPii = true; // Metadata headers will appear in span data
});
await Sentry.init((options) {
options.dsn = '___PUBLIC_DSN___';
options.sendDefaultPii = true; // Metadata headers will appear in span data
});
Before enabling tracing:
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();
}
}
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.
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").