JustAnalytics for Gin (Go)

Step-by-step guide to add analytics, error tracking, and APM to your Gin app

JustAnalytics for Gin (Go)

Add distributed tracing, error tracking, structured logging, and infrastructure metrics to your Gin application.

Time to data: 3-5 minutes

Prerequisites#


Install the SDK

go get github.com/justanalyticsapp/justanalytics-go

Initialize the SDK

package main

import (
    "log"
    "os"

    "github.com/gin-gonic/gin"
    justanalytics "github.com/justanalyticsapp/justanalytics-go"
    "github.com/justanalyticsapp/justanalytics-go/middleware"
)

func main() {
    err := justanalytics.Init(justanalytics.Options{
        SiteID:      os.Getenv("JA_SITE_ID"),
        APIKey:      os.Getenv("JA_API_KEY"),
        ServiceName: "gin-api",
        Environment: os.Getenv("GO_ENV"),
        Release:     os.Getenv("APP_VERSION"),
    })
    if err != nil {
        log.Fatal(err)
    }
    defer justanalytics.Close()

    r := gin.Default()

    // Add JustAnalytics tracing middleware
    r.Use(func(c *gin.Context) {
        middleware.GinHandler(c.Request, c.Writer, c.FullPath(), c.Next)
    })

    // Your routes
    r.GET("/api/users/:id", getUser)
    r.POST("/api/orders", createOrder)

    r.Run(":8080")
}

Add your handlers

Requests are automatically traced by the middleware. Each request creates a span named after the Gin route pattern:

func getUser(c *gin.Context) {
    // Automatically traced as "GET /api/users/:id"
    id := c.Param("id")

    user, err := db.FindUser(c.Request.Context(), id)
    if err != nil {
        c.JSON(404, gin.H{"error": "User not found"})
        return
    }

    c.JSON(200, user)
}

func createOrder(c *gin.Context) {
    var req CreateOrderRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    order, err := db.CreateOrder(c.Request.Context(), req)
    if err != nil {
        c.JSON(500, gin.H{"error": "Failed to create order"})
        return
    }

    c.JSON(201, order)
}

Add error tracking

Capture errors explicitly in your handlers:

func processPayment(c *gin.Context) {
    ctx := c.Request.Context()

    charge, err := stripe.CreateCharge(ctx, amount)
    if err != nil {
        justanalytics.CaptureException(ctx, err,
            justanalytics.WithTags(map[string]string{"module": "payments"}),
            justanalytics.WithExtra(map[string]interface{}{"amount": amount}),
        )
        c.JSON(500, gin.H{"error": "Payment failed"})
        return
    }

    c.JSON(200, charge)
}

Add a global recovery middleware:

r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
    if err, ok := recovered.(error); ok {
        justanalytics.CaptureException(c.Request.Context(), err)
    } else {
        justanalytics.CaptureMessage(c.Request.Context(),
            fmt.Sprintf("Panic: %v", recovered),
            justanalytics.ErrorLevelFatal,
        )
    }
    c.AbortWithStatusJSON(500, gin.H{"error": "Internal server error"})
}))

Verify data is flowing

Run your server and send a test request:

go run main.go
curl http://localhost:8080/api/users/1

Verify Your Setup

After sending a request, check the Traces tab. You should see spans with Gin route patterns like 'GET /api/users/:id'.

Open Dashboard

Advanced Features#

Custom spans#

func getReport(c *gin.Context) {
    ctx := c.Request.Context()
    reportID := c.Param("id")

    ctx, span := justanalytics.StartSpan(ctx, "generate-report",
        justanalytics.WithSpanKind(justanalytics.SpanKindServer),
    )
    defer span.End()
    span.SetAttribute("report.id", reportID)

    // Nested span for database query
    ctx, dbSpan := justanalytics.StartSpan(ctx, "db.fetch-report",
        justanalytics.WithSpanKind(justanalytics.SpanKindClient),
    )
    report, err := db.GetReport(ctx, reportID)
    dbSpan.End()

    if err != nil {
        span.SetStatus(justanalytics.SpanStatusError, err.Error())
        c.JSON(500, gin.H{"error": "Failed to fetch report"})
        return
    }

    c.JSON(200, report)
}

User identification#

// Auth middleware
func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        user, err := validateToken(c.GetHeader("Authorization"))
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
            return
        }

        ctx := justanalytics.SetUser(c.Request.Context(), justanalytics.UserInfo{
            ID:    user.ID,
            Email: user.Email,
        })
        c.Request = c.Request.WithContext(ctx)
        c.Next()
    }
}

Outgoing HTTP tracing#

Wrap your HTTP client transport to automatically trace outgoing requests:

client := &http.Client{
    Transport: middleware.HTTPTransport(http.DefaultTransport),
}

// All outgoing requests now propagate W3C traceparent headers
resp, err := client.Get("https://api.stripe.com/v1/charges")

Database instrumentation#

import "github.com/justanalyticsapp/justanalytics-go/instrumentation"

db, _ := sql.Open("postgres", dsn)
wdb := instrumentation.WrapDB(db, "mydb")

// All queries are now traced
rows, err := wdb.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", id)

Structured logging#

justanalytics.RecordLog(ctx, "info", "User logged in",
    map[string]interface{}{"userId": "u123"},
)
justanalytics.RecordLog(ctx, "error", "Payment failed",
    map[string]interface{}{"orderId": "o456", "reason": "declined"},
)

Custom metrics#

justanalytics.RecordMetric("custom.goroutines", float64(runtime.NumGoroutine()),
    map[string]interface{}{"service": "gin-api"},
)
justanalytics.RecordMetric("custom.queue_size", float64(queue.Len()), nil)

Environment variables#

| Variable | Description | Required | |----------|-------------|----------| | JA_SITE_ID | Your site ID from the dashboard | Yes | | JA_API_KEY | API key (starts with ja_sk_) | Yes | | GO_ENV | Deployment environment | No | | APP_VERSION | Release version for tracking | No |