Skip to main content
Metlo provides native middleware for Gorilla Mux and the standard Go HTTP server.

Installation

Install the Metlo Gorilla package:
go get github.com/metlo-labs/metlo/ingestors/golang/gorilla
go get github.com/metlo-labs/metlo/ingestors/golang/metlo

Quick Start

Add Metlo middleware to your Gorilla Mux application:
package main

import (
    "net/http"
    "github.com/gorilla/mux"
    metloGorilla "github.com/metlo-labs/metlo/ingestors/golang/gorilla"
    "github.com/metlo-labs/metlo/ingestors/golang/metlo"
)

func main() {
    // Initialize Metlo
    metloApp := metlo.InitMetlo(
        "<YOUR_METLO_COLLECTOR_URL>",
        "<YOUR_METLO_API_KEY>",
    )
    
    // Create router
    r := mux.NewRouter()
    
    // Add Metlo middleware
    metloMiddleware := metloGorilla.Init(metloApp)
    r.Use(metloMiddleware.Middleware)
    
    // Define routes
    r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"message": "Hello World"}`))
    })
    
    http.ListenAndServe(":8080", r)
}

Configuration

Basic Initialization

The simplest setup uses default configuration:
metloApp := metlo.InitMetlo(
    "https://collector.metlo.com",
    "your_api_key_here",
)

metloMiddleware := metloGorilla.Init(metloApp)
r.Use(metloMiddleware.Middleware)

Custom Initialization

For advanced configuration, use CustomInit:
metloApp := metlo.InitMetlo(
    "https://collector.metlo.com",
    "your_api_key_here",
)

metloMiddleware := metloGorilla.CustomInit(
    metloApp,
    "api.example.com",  // Server host
    8080,                // Server port
    getUserFunc,         // Optional user extraction function
)

r.Use(metloMiddleware.Middleware)
app
metloApp
required
Initialized Metlo application instance
serverHost
string
default:"localhost"
The hostname of your server for trace metadata
serverPort
int
default:"0"
The port your server is listening on
getUser
func(*http.Request) *string
default:"nil"
Optional function to extract user identifier from request

User Tracking

Extract user information from requests:
func getUserFromRequest(r *http.Request) *string {
    // Extract from JWT, session, header, etc.
    userID := r.Header.Get("X-User-ID")
    if userID == "" {
        return nil
    }
    return &userID
}

metloMiddleware := metloGorilla.CustomInit(
    metloApp,
    "api.example.com",
    8080,
    getUserFromRequest,
)

How It Works

The Gorilla middleware:
  1. Wraps response writer - Custom writer captures status code and body
  2. Reads request body - Captures and restores request body
  3. Rate limiting - Checks if request should be traced
  4. Blocking - Can block requests based on security rules
  5. Calls next handler - Your handler executes normally
  6. Asynchronous transmission - Sends traces to Metlo in goroutine

Request Blocking

Metlo can block malicious requests:
if m.app.ShouldBlock(request, meta) {
    logRespWriter.statusCode = 403
    logRespWriter.WriteHeader(http.StatusForbidden)
    logRespWriter.Write([]byte("Forbidden"))
    return
}

Captured Data

For each request, Metlo captures: Request:
  • URL (host, path, query parameters)
  • HTTP method
  • Headers
  • Request body (up to 10KB)
  • Source IP and port
  • User identifier (if configured)
Response:
  • Status code (defaults to 200 if not set)
  • Headers
  • Response body (up to 10KB)
Metadata:
  • Source IP and port
  • Destination host and port
  • Environment: production
  • Source identifier: go/gorilla

Full Example Application

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"
    
    "github.com/gorilla/mux"
    metloGorilla "github.com/metlo-labs/metlo/ingestors/golang/gorilla"
    "github.com/metlo-labs/metlo/ingestors/golang/metlo"
)

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

func getUserFromRequest(r *http.Request) *string {
    // Extract from Authorization header, session, etc.
    authHeader := r.Header.Get("Authorization")
    if authHeader != "" {
        // Parse token and extract user ID
        // This is a simplified example
        return &authHeader
    }
    return nil
}

func getUsersHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "users": []User{},
    })
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(map[string]string{
            "error": err.Error(),
        })
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(map[string]interface{}{
        "success": true,
        "user":    user,
    })
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.Write([]byte(`{"status": "healthy"}`))
}

func main() {
    // Initialize Metlo
    metloApp := metlo.InitMetlo(
        os.Getenv("METLO_COLLECTOR_URL"),
        os.Getenv("METLO_API_KEY"),
    )
    
    // Create router
    r := mux.NewRouter()
    
    // Add Metlo middleware
    metloMiddleware := metloGorilla.CustomInit(
        metloApp,
        os.Getenv("SERVER_HOST"),
        8080,
        getUserFromRequest,
    )
    r.Use(metloMiddleware.Middleware)
    
    // API routes
    api := r.PathPrefix("/api").Subrouter()
    api.HandleFunc("/users", getUsersHandler).Methods("GET")
    api.HandleFunc("/users", createUserHandler).Methods("POST")
    
    // Health check
    r.HandleFunc("/health", healthHandler).Methods("GET")
    
    // Start server
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", r))
}

Using with Standard HTTP

Metlo Gorilla middleware works with standard Go HTTP server without Gorilla Mux:
package main

import (
    "net/http"
    metloGorilla "github.com/metlo-labs/metlo/ingestors/golang/gorilla"
    "github.com/metlo-labs/metlo/ingestors/golang/metlo"
)

func main() {
    metloApp := metlo.InitMetlo(
        "https://collector.metlo.com",
        "your_api_key_here",
    )
    
    metloMiddleware := metloGorilla.Init(metloApp)
    
    // Wrap your handler with Metlo middleware
    http.Handle("/", metloMiddleware.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })))
    
    http.ListenAndServe(":8080", nil)
}

Status Code Handling

If your handler doesn’t explicitly call WriteHeader(), the response writer defaults to 200:
statusCode := logRespWriter.statusCode
if statusCode == 0 {
    statusCode = 200
}

Body Size Limits

Metlo captures up to 10KB of request and response bodies:
const MAX_BODY int = 10 * 1024  // 10KB
Larger bodies are automatically truncated.

Environment Variables

Example using environment variables:
import "os"

func main() {
    metloApp := metlo.InitMetlo(
        os.Getenv("METLO_COLLECTOR_URL"),
        os.Getenv("METLO_API_KEY"),
    )
    
    metloMiddleware := metloGorilla.CustomInit(
        metloApp,
        os.Getenv("SERVER_HOST"),
        8080,
        nil,
    )
    
    r := mux.NewRouter()
    r.Use(metloMiddleware.Middleware)
    
    // ... routes ...
    
    http.ListenAndServe(":8080", r)
}
Set environment variables:
export METLO_COLLECTOR_URL="https://collector.metlo.com"
export METLO_API_KEY="your_api_key_here"
export SERVER_HOST="api.example.com"

Troubleshooting

Verify:
  • Middleware is added with r.Use(metloMiddleware.Middleware)
  • Middleware is added before route handlers
  • Metlo app is initialized correctly
  • Collector URL is accessible
You may see “Metlo couldn’t find source port” in logs. This is a warning and won’t prevent trace collection. The source port field will be missing from traces.
Metlo reads and restores the request body. If you’re reading r.Body before the middleware runs, ensure Metlo middleware is first in the chain.
Ensure you’re writing responses through the provided ResponseWriter. Metlo wraps this writer to capture data.

Middleware Order

Middleware executes in the order it’s added. Place Metlo middleware early to capture all requests:
r.Use(metloMiddleware.Middleware)  // First
r.Use(loggingMiddleware)           // Second
r.Use(authMiddleware)              // Third
// ... route handlers

Requirements

  • Go >= 1.11
  • Optional: Gorilla Mux (works with standard library too)

Next Steps

API Discovery

View your discovered Gorilla APIs

Gin Integration

Use Metlo with Gin framework

Build docs developers (and LLMs) love