# gRPC-Gateway

> Instrument Go gRPC-Gateway applications with the Last9 Go Agent for automatic HTTP and gRPC tracing across the full request stack

Source: https://last9.io/docs/integrations/frameworks/go/grpc-gateway/

Use the Last9 Go Agent to instrument your gRPC-Gateway application with automatic traces and metrics across the full stack. [gRPC-Gateway](https://grpc-ecosystem.github.io/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**

   ```bash
   go get github.com/last9/go-agent
   ```

2. **Set Environment Variables**

   ```bash
   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:

   ```go
   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

| 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

```go
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

```go
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:

```go
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

```yaml
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](https://app.last9.io/traces) and [Metrics Explorer](https://app.last9.io/metrics) in Last9.

:::tip
The trace topology diagram in Last9 will show both HTTP and gRPC spans in a single trace, letting you see end-to-end latency across the gateway boundary.
:::

---

## Troubleshooting

Please get in touch with us on [Discord](https://discord.com/invite/Q3p2EEucx9) or [Email](mailto:support@last9.io) if you have any questions.
