grpcclient

grpcclient returns a plain grpc.ClientConn with trace ID propagation, one log entry per RPC, default deadlines, TLS by default, and native retry policies.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/grpcclient"

Builds a preconfigured *grpc.ClientConn for outbound gRPC calls, the counterpart of httpclient and httpretrier.

conn, err := grpcclient.New("dns:///items.internal:8074",
    grpcclient.WithComponent("items"),
    grpcclient.WithTimeout(5*time.Second),
)
defer conn.Close()

client := itemv1.NewItemServiceClient(conn)

A plain ClientConn

New calls grpc.NewClient and returns its result. Every behaviour is added through interceptors and dial options, so generated stubs, health clients and any other gRPC tooling work unchanged, and there is no wrapper type to thread through the code.

Create the connection once and share it: it is safe for concurrent use and multiplexes calls over HTTP/2.

What every call gets

StepWhat it does
Trace propagationcopies the traceid from the context into the x-request-id metadata, generating a UUIDv7 when there is none
Loggerone outbound entry per RPC with target, service, method, grpc_type, grpc_code and timing
Default deadlinegives a unary call without a deadline the one set by WithTimeout, 1 minute by default
Your interceptorsWithUnaryInterceptor and WithStreamInterceptor, in order

The trace ID is the same value the HTTP client sends in X-Request-ID, so a request that crosses both transports keeps one ID in every log.

The default deadline matters because a gRPC call without one waits forever. Streams are exempt.

Logging levels follow the status code

An RPC is logged at error level only when its code maps to a 5xx status: UNKNOWN, DEADLINE_EXCEEDED, UNIMPLEMENTED, INTERNAL, UNAVAILABLE, DATA_LOSS. NOT_FOUND, INVALID_ARGUMENT and the other application outcomes are logged at info, or at debug when debug logging is on.

That keeps error-level alerting on failures of the downstream service rather than on lookups that legitimately found nothing.

At debug level, unary messages are dumped as protobuf JSON, redacted, and omitted above WithMaxDumpSize. A failed RPC logs its error through the RedactFn at every level.

A stream is logged once, when it ends. As grpc-go requires, read a stream until it returns an error or cancel its context; otherwise it is never logged and its resources are never released.

TLS unless you say otherwise

TLS with the system root certificates and TLS 1.2 as the minimum is the default. WithTLSConfig sets a private CA or a client certificate, WithCredentialsBundle uses Google default, ALTS or xDS credentials, and plaintext needs an explicit WithInsecure.

Combining more than one of those is an error from New (ErrConflictingTransportSecurity), so a configuration that sets both TLS and plaintext cannot silently pick one.

Retries belong to grpc-go

WithRetryPolicy compiles a policy into the default service config, and grpc-go executes it:

grpcclient.WithRetryPolicy(grpcclient.RetryPolicy{
    Methods:           []string{itemv1.ItemService_GetItem_FullMethodName},
    MaxAttempts:       3,
    InitialBackoff:    100 * time.Millisecond,
    MaxBackoff:        time.Second,
    BackoffMultiplier: 2,
    RetryableCodes:    []codes.Code{codes.Unavailable},
})

The native mechanism is transport-aware in ways an interceptor cannot be. It uses exponential backoff with jitter, honours the call deadline, and stops once response headers arrive, so a stream is never replayed after the server has answered. grpc-go also retries, transparently and independently of any policy, a call that never reached the server application.

Scope each policy to methods that are safe to repeat. grpc-go caps attempts at 5; WithMaxCallAttempts changes the cap. For logic the service config cannot express, wrap the call with retrier.

Instrumentation

WithInstrumenter adds the dial options of a grpcutil.ClientInstrumenter. prometheusgrpc and opentelgrpc implement it, and one value of either can be passed to both the server and the client.

Dependencies

Reaches google.golang.org/grpc, google.golang.org/protobuf, google.golang.org/genproto/googleapis/rpc, and golang.org/x/net, x/sys and x/text.