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.


Troubleshooting

Please get in touch with us on Discord or Email if you have any questions.