This example demonstrates how you can attach OpenTelemetry tracing to outgoing gRPC client request and server response. As this is a follow up to the previous example linked below I am cutting it short.


Config


Add this file to the previous post - Implementing OpenTelemetry and Jaeger tracing in Golang HTTP API. Also make sure to run go get -u go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc command as well.


// grpc.go

package trace

import (
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
)

// NewGRPUnaryClientInterceptor returns unary client interceptor. It is used
// with `grpc.WithUnaryInterceptor` method.
func NewGRPUnaryClientInterceptor() grpc.UnaryClientInterceptor {
return otelgrpc.UnaryClientInterceptor()
}

// NewGRPUnaryServerInterceptor returns unary server interceptor. It is used
// with `grpc.UnaryInterceptor` method.
func NewGRPUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return otelgrpc.UnaryServerInterceptor()
}

// NewGRPCStreamClientInterceptor returns stream client interceptor. It is used
// with `grpc.WithStreamInterceptor` method.
func NewGRPCStreamClientInterceptor() grpc.StreamClientInterceptor {
return otelgrpc.StreamClientInterceptor()
}

// NewGRPCStreamClientInterceptor returns stream server interceptor. It is used
// with `grpc.StreamInterceptor` method.
func NewGRPCStreamServerInterceptor() grpc.StreamServerInterceptor {
return otelgrpc.StreamServerInterceptor()
}

Protobuf


syntax = "proto3";

package bank;

option go_package = "github.com/you/{client|server}/pkg/protobuf/bank;bankpb";

service AccountService {
rpc Create(CreateRequest) returns (CreateResponse) {}
}

message CreateRequest {
string name = 1;
}

message CreateResponse {
bool success = 1;
}

Client


main.go


package main

import (
"context"
"log"

"github.com/you/client/internal/bank"
"github.com/you/client/internal/pkg/trace"

"google.golang.org/grpc"
)

func main() {
ctx := context.Background()

// Bootstrap tracer.
prv, err := trace.NewProvider(trace.ProviderConfig{
JaegerEndpoint: "http://localhost:14268/api/traces",
ServiceName: "client",
ServiceVersion: "1.0.0",
Environment: "dev",
Disabled: false,
})
if err != nil {
log.Fatalln(err)
}
defer prv.Close(ctx)

// Bootstrap gRPC client.
conn, err := grpc.Dial(":50051", grpc.WithInsecure(),
grpc.WithUnaryInterceptor(trace.NewGRPUnaryClientInterceptor()),
)
if err != nil {
log.Fatalln(err)
}
defer conn.Close()

// Bootstrap bank gRPC client and issue requests.
bankAccServie := bank.NewAccountService(conn)
if err := bankAccServie.Create(context.Background()); err != nil {
log.Println(err)
}
}

account_service.go


package bank

import (
"context"
"fmt"

"google.golang.org/grpc"

bankpb "github.com/you/client/pkg/protobuf/bank"
)

type AccountService struct {
client bankpb.AccountServiceClient
}

func NewAccountService(conn grpc.ClientConnInterface) AccountService {
return AccountService{
client: bankpb.NewAccountServiceClient(conn),
}
}

func (a AccountService) Create(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

res, err := a.client.Create(ctx, &bankpb.CreateRequest{Name: "Joe"})
if err != nil {
return err
}

fmt.Println(res)

return nil
}

Server


main.go


package main

import (
"context"
"log"
"net"

"github.com/you/server/internal/bank"
"github.com/you/server/internal/pkg/trace"

"google.golang.org/grpc"

bankpb "github.com/you/server/pkg/protobuf/bank"
)

func main() {
ctx := context.Background()

// Bootstrap tracer.
prv, err := trace.NewProvider(trace.ProviderConfig{
JaegerEndpoint: "http://localhost:14268/api/traces",
ServiceName: "server",
ServiceVersion: "2.0.0",
Environment: "dev",
Disabled: false,
})
if err != nil {
log.Fatalln(err)
}
defer prv.Close(ctx)

// Bootstrap listener.
listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln(err)
}
defer listener.Close()

// Bootstrap gRPC server.
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(trace.NewGRPUnaryServerInterceptor()),
)

// Bootstrap bank gRPC service server and respond to requests.
bankAccService := bank.AccountService{}
bankpb.RegisterAccountServiceServer(grpcServer, bankAccService)

log.Fatalln(grpcServer.Serve(listener))
}

account_service.go


package bank

import (
"context"
"fmt"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

bankpb "github.com/you/server/pkg/protobuf/bank"
)

type AccountService struct{}

func (AccountService) Create(ctx context.Context, req *bankpb.CreateRequest) (*bankpb.CreateResponse, error) {
if req.GetName() != "Joe" {
return nil, status.Errorf(codes.InvalidArgument, "create account: unexpected name: %s", req.GetName())
}

fmt.Println(req)

return &bankpb.CreateResponse{Success: true}, nil
}

Result


Success






Failure