Skip to content
Last9
Book demo

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/v2
  • google.golang.org/grpc
  • Last9 account with OTLP credentials

Installation

  1. Install the Last9 Go Agent

    go get github.com/last9/go-agent
  2. 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"
  3. Instrument your application

    Each layer has a dedicated constructor that auto-wires instrumentation:

    package main
    import (
    "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 RPCs
    grpcServer := 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 → gRPC
    ctx := context.Background()
    gwMux := grpcgateway.NewGatewayMux()
    // Connect gateway to gRPC server with instrumented dial option
    // NewDialOption propagates trace context from gateway to gRPC server
    conn, 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 spans
    httpMux := 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

ConstructorLayerSignals collected
grpcgateway.NewGrpcServer()gRPC serverSpans per RPC, rpc.server.duration, request/response size metrics
grpcgateway.NewGatewayMux()Gateway muxPasses trace context through, no extra spans
grpcgateway.NewDialOption()gRPC clientClient-side spans, traceparent propagation
grpcgateway.WrapHTTPMux(mux, name)HTTP serverHTTP 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 traced
func (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/v1
kind: Deployment
metadata:
name: grpc-gateway-app
spec:
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.