gRPC-Gateway
Instrument Go gRPC-Gateway applications with the Last9 Go Agent for automatic HTTP and gRPC tracing across the full request stack
Use the Last9 Go Agent to instrument your gRPC-Gateway application with automatic traces and metrics across the full stack. gRPC-Gateway translates HTTP/JSON requests into gRPC calls — the agent instruments all three layers so every request produces connected spans from the HTTP edge to the gRPC backend.
How It Works
HTTP client → WrapHTTPMux → NewGatewayMux → NewDialOption → NewGrpcServer (HTTP spans) (transcoding) (gRPC client) (gRPC spans)Prerequisites
- Go 1.22 or higher
github.com/grpc-ecosystem/grpc-gateway/v2google.golang.org/grpc- Last9 account with OTLP credentials
Installation
-
Install the Last9 Go Agent
go get github.com/last9/go-agent -
Set Environment Variables
export OTEL_SERVICE_NAME="your-grpc-gateway-service"export OTEL_EXPORTER_OTLP_ENDPOINT="$last9_otlp_endpoint"export OTEL_EXPORTER_OTLP_HEADERS="Authorization=$last9_otlp_auth_header"export OTEL_TRACES_SAMPLER="always_on"export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production" -
Instrument your application
Each layer has a dedicated constructor that auto-wires instrumentation:
package mainimport ("context""log""net""net/http""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""github.com/last9/go-agent""github.com/last9/go-agent/instrumentation/grpcgateway"pb "your-module/gen/proto")func main() {if err := agent.Start(); err != nil {log.Fatalf("Failed to start agent: %v", err)}defer agent.Shutdown()// Layer 1: gRPC server — traces all unary + streaming RPCsgrpcServer := grpcgateway.NewGrpcServer()pb.RegisterYourServiceServer(grpcServer, &serviceImpl{})lis, err := net.Listen("tcp", ":9090")if err != nil {log.Fatalf("failed to listen: %v", err)}go grpcServer.Serve(lis)// Layer 2: gRPC-Gateway mux — translates HTTP/JSON → gRPCctx := context.Background()gwMux := grpcgateway.NewGatewayMux()// Connect gateway to gRPC server with instrumented dial option// NewDialOption propagates trace context from gateway to gRPC serverconn, err := grpc.NewClient("localhost:9090",grpc.WithTransportCredentials(insecure.NewCredentials()),grpcgateway.NewDialOption(),)if err != nil {log.Fatalf("failed to dial: %v", err)}defer conn.Close()pb.RegisterYourServiceHandler(ctx, gwMux, conn)// Layer 3: HTTP server — captures HTTP-level metrics and spanshttpMux := http.NewServeMux()httpMux.Handle("/", gwMux)handler := grpcgateway.WrapHTTPMux(httpMux, "my-service")log.Println("HTTP gateway :8080, gRPC server :9090")log.Fatal(http.ListenAndServe(":8080", handler))}
What Each Constructor Instruments
| Constructor | Layer | Signals collected |
|---|---|---|
grpcgateway.NewGrpcServer() | gRPC server | Spans per RPC, rpc.server.duration, request/response size metrics |
grpcgateway.NewGatewayMux() | Gateway mux | Passes trace context through, no extra spans |
grpcgateway.NewDialOption() | gRPC client | Client-side spans, traceparent propagation |
grpcgateway.WrapHTTPMux(mux, name) | HTTP server | HTTP spans, http.server.request.duration, active requests gauge |
Database Instrumentation
import "github.com/last9/go-agent/integrations/database"
db, err := database.Open(database.Config{ DriverName: "postgres", DSN: "postgres://user:pass@localhost/mydb", DatabaseName: "mydb",})defer db.Close()
// Use in your gRPC handler — queries are automatically tracedfunc (s *serviceImpl) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) { row := db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id = $1", req.Id) // ...}Supported databases: PostgreSQL, MySQL, SQLite.
Redis Instrumentation
import redisagent "github.com/last9/go-agent/integrations/redis"
rdb := redisagent.NewClient(&redis.Options{ Addr: "localhost:6379",})
func (s *serviceImpl) GetSession(ctx context.Context, req *pb.SessionRequest) (*pb.Session, error) { val, err := rdb.Get(ctx, "session:"+req.Id).Result() // ...}Outbound HTTP Calls
For calling other HTTP services from your gRPC handlers:
import httpagent "github.com/last9/go-agent/integrations/http"
client := httpagent.NewClient(&http.Client{Timeout: 10 * time.Second})
func (s *serviceImpl) EnrichUser(ctx context.Context, req *pb.EnrichRequest) (*pb.EnrichedUser, error) { httpReq, _ := http.NewRequestWithContext(ctx, "GET", "https://profile-svc/user/"+req.Id, nil) resp, err := client.Do(httpReq) // traceparent header injected automatically // ...}Trace Topology
Every HTTP request through gRPC-Gateway produces a connected span tree in Last9:
HTTP span (http.server — port 8080) └─ gRPC client span (rpc.client — gateway → gRPC) └─ gRPC server span (rpc.server — port 9090) ├─ DB span (db.postgresql) └─ Redis span (redis)W3C traceparent headers flow automatically across all layer boundaries.
Kubernetes Deployment
apiVersion: apps/v1kind: Deploymentmetadata: name: grpc-gateway-appspec: template: spec: containers: - name: grpc-gateway-app image: your-registry/grpc-gateway-app:latest ports: - name: http containerPort: 8080 - name: grpc containerPort: 9090 env: - name: OTEL_SERVICE_NAME value: "grpc-gateway-app" - name: OTEL_EXPORTER_OTLP_ENDPOINT valueFrom: secretKeyRef: name: last9-credentials key: endpoint - name: OTEL_EXPORTER_OTLP_HEADERS valueFrom: secretKeyRef: name: last9-credentials key: auth-header - name: OTEL_RESOURCE_ATTRIBUTES value: "deployment.environment=production"View Traces and Metrics
After running your application, navigate to Trace Explorer and Metrics Explorer in Last9.