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.
Troubleshooting
Please get in touch with us on Discord or Email if you have any questions.