Hello everyone!

We have been investing plenty of personal time and energy for many years to share our knowledge with you all. However, we now need your help to keep this blog running. All you have to do is just click one of the adverts on the site, otherwise it will sadly be taken down due to hosting etc. costs. Thank you.

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