Tutorial

Building Robust Analytics with CounterAPI in Go Applications

Learn how to leverage CounterAPI's native Go client to implement powerful metrics tracking and analytics in your Golang applications with minimal setup.

DA

Tutorial Expert

• 6 min read

CounterAPI Go Golang Analytics Backend API

Why Go Developers Need Simple Analytics Solutions

In the world of Go development, where performance and simplicity reign supreme, analytics solutions often add unnecessary complexity to your codebase. Traditional analytics require setting up databases, managing schemas, and implementing complex data pipelines—distractions from building your core application.

CounterAPI offers Go developers a refreshingly simple alternative: a native Go client that provides powerful metrics tracking with just a few lines of code. Whether you're building microservices, CLI tools, or web applications, CounterAPI's Go client helps you track what matters without the overhead.

Getting Started with CounterAPI in Go

Installation

Adding the CounterAPI client to your Go project is straightforward:

go get github.com/counterapi/api/pkg/client

This command adds the CounterAPI Go client to your project's dependencies.

Basic Setup

The Go client supports both V1 and V2 of the CounterAPI endpoints, with V2 being the recommended option for new projects. Here's a simple setup:

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/counterapi/api/pkg/client"
)

func main() {
	// Create a new CounterAPI client
	counter := client.New(client.Options{
		Version:   client.V2, // Use V2 API (recommended)
		Workspace: "my-go-app",
		// Optional configuration
		// Debug:      true,
		// Timeout:    5 * time.Second,
		// AccessToken: "your-token", // For V2 API authentication
	})

	// Use the client
	ctx := context.Background()
	
	// Increment a counter
	result, err := counter.Up(ctx, "app-starts")
	if err != nil {
		log.Fatalf("Failed to increment counter: %v", err)
	}
	
	fmt.Printf("App has been started %d times\n", result.Value)
}

With just this code, you've established a metrics tracking system for your Go application!

Practical Examples

Let's explore some real-world examples where CounterAPI's Go client shines.

Example 1: HTTP Server with Request Tracking

Here's how to track API endpoint usage in a standard Go HTTP server:

package main

import (
	"context"
	"encoding/json"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/counterapi/api/pkg/client"
)

// Global counter client - thread-safe for concurrent use
var counter = client.New(client.Options{
	Version:   client.V2,
	Workspace: "go-api-server",
})

// Middleware for tracking API requests
func trackAPIRequest(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Get path and sanitize it for use as counter name
		path := strings.ReplaceAll(r.URL.Path, "/", "-")
		if path == "" || path == "-" {
			path = "root"
		}
		
		// Use a short timeout for analytics
		ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
		defer cancel()
		
		// Track this endpoint usage asynchronously
		go func() {
			_, err := counter.Up(ctx, "endpoint"+path)
			if err != nil {
				log.Printf("Analytics error: %v", err)
			}
		}()
		
		// Continue with the request
		next.ServeHTTP(w, r)
	})
}

// Handler for the home page
func homeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"message": "Welcome to the API!",
	})
}

// Handler for getting user data
func usersHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode([]map[string]interface{}{
		{"id": 1, "name": "Alice", "role": "Admin"},
		{"id": 2, "name": "Bob", "role": "User"},
	})
}

// Handler for analytics data
func statsHandler(w http.ResponseWriter, r *http.Request) {
	ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
	defer cancel()
	
	// Get stats for each endpoint
	rootStats, err1 := counter.Get(ctx, "endpoint-root")
	userStats, err2 := counter.Get(ctx, "endpoint-users")
	
	// Prepare response
	stats := map[string]interface{}{}
	
	if err1 == nil {
		stats["home_visits"] = rootStats.Value
	}
	
	if err2 == nil {
		stats["users_api_calls"] = userStats.Value
	}
	
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(stats)
}

func main() {
	// Create a router
	mux := http.NewServeMux()
	
	// Register handlers
	mux.HandleFunc("/", homeHandler)
	mux.HandleFunc("/users", usersHandler)
	mux.HandleFunc("/stats", statsHandler)
	
	// Apply middleware
	handler := trackAPIRequest(mux)
	
	// Start server
	log.Println("Server started at :8080")
	if err := http.ListenAndServe(":8080", handler); err != nil {
		log.Fatalf("Server failed: %v", err)
	}
}

This implementation:

  1. Creates a global counter client safe for concurrent use
  2. Uses middleware to track all API requests by endpoint
  3. Processes analytics asynchronously to avoid impacting response times
  4. Provides a stats endpoint to check the current metrics

Example 2: CLI Application Usage Tracking

For CLI tools, tracking command usage can provide valuable insights:

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"
	
	"github.com/counterapi/api/pkg/client"
	"github.com/spf13/cobra"
)

var counter = client.New(client.Options{
	Version:   client.V2,
	Workspace: "my-cli-tool",
})

// Function to track command usage
func trackCommand(cmdName string) {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	
	// Track command usage in the background
	go func() {
		_, err := counter.Up(ctx, "cmd-"+cmdName)
		if err != nil {
			// Simply log errors, don't interrupt the command execution
			log.Printf("Analytics error: %v", err)
		}
	}()
}

func main() {
	// Root command
	rootCmd := &cobra.Command{
		Use:   "mycli",
		Short: "A sample CLI application with usage tracking",
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			// Track the command being executed
			trackCommand(cmd.Name())
		},
	}
	
	// Add subcommands
	generateCmd := &cobra.Command{
		Use:   "generate",
		Short: "Generate something",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Generating content...")
			// Command implementation
		},
	}
	
	analyzeCmd := &cobra.Command{
		Use:   "analyze",
		Short: "Analyze something",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Analyzing content...")
			// Command implementation
		},
	}
	
	// Add usage command to show analytics data
	usageCmd := &cobra.Command{
		Use:   "usage",
		Short: "Show usage statistics",
		Run: func(cmd *cobra.Command, args []string) {
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			defer cancel()
			
			// Get stats for each command
			rootUsage, _ := counter.Get(ctx, "cmd-mycli")
			generateUsage, _ := counter.Get(ctx, "cmd-generate")
			analyzeUsage, _ := counter.Get(ctx, "cmd-analyze")
			
			fmt.Println("Command Usage Statistics:")
			fmt.Printf("- Root command: %d executions\n", rootUsage.Value)
			fmt.Printf("- Generate command: %d executions\n", generateUsage.Value)
			fmt.Printf("- Analyze command: %d executions\n", analyzeUsage.Value)
		},
	}
	
	// Add commands to root
	rootCmd.AddCommand(generateCmd, analyzeCmd, usageCmd)
	
	// Execute
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

This example:

  1. Integrates CounterAPI with a Cobra-based CLI application
  2. Tracks each command execution via PersistentPreRun
  3. Provides a usage command to display analytics
  4. Handles analytics in the background to avoid slowing down commands

Example 3: Concurrent Service with Context Support

Go excels at handling concurrent operations, and CounterAPI's Go client is designed to work well in these scenarios:

package main

import (
	"context"
	"fmt"
	"log"
	"sync"
	"time"
	
	"github.com/counterapi/api/pkg/client"
)

type JobProcessor struct {
	counter *client.Client
	wg      sync.WaitGroup
}

func NewJobProcessor() *JobProcessor {
	return &JobProcessor{
		counter: client.New(client.Options{
			Version:   client.V2,
			Workspace: "job-processor",
			Timeout:   3 * time.Second,
		}),
	}
}

func (p *JobProcessor) ProcessJobs(ctx context.Context, jobs []string) {
	// Create a cancellable context for all jobs
	jobCtx, cancel := context.WithCancel(ctx)
	defer cancel()
	
	// Track overall job batch
	batchResult, err := p.counter.Up(jobCtx, "job-batch-started")
	if err != nil {
		log.Printf("Failed to track batch start: %v", err)
	} else {
		log.Printf("This is batch #%d", batchResult.Value)
	}
	
	// Process jobs concurrently
	p.wg.Add(len(jobs))
	for i, job := range jobs {
		// Use goroutine to process jobs in parallel
		go p.processJob(jobCtx, i, job)
	}
	
	// Wait for all jobs to complete
	p.wg.Wait()
	
	// Track batch completion
	_, err = p.counter.Up(ctx, "job-batch-completed")
	if err != nil {
		log.Printf("Failed to track batch completion: %v", err)
	}
}

func (p *JobProcessor) processJob(ctx context.Context, id int, jobType string) {
	defer p.wg.Done()
	
	// Start job and track it
	log.Printf("Starting job #%d of type %s", id, jobType)
	_, err := p.counter.Up(ctx, fmt.Sprintf("job-%s-started", jobType))
	if err != nil {
		log.Printf("Failed to track job start: %v", err)
	}
	
	// Simulate job processing
	select {
	case <-time.After(1 * time.Second):
		// Job completed successfully
		_, err := p.counter.Up(ctx, fmt.Sprintf("job-%s-completed", jobType))
		if err != nil {
			log.Printf("Failed to track job completion: %v", err)
		}
		log.Printf("Job #%d completed", id)
	case <-ctx.Done():
		// Context was cancelled
		_, err := p.counter.Up(context.Background(), fmt.Sprintf("job-%s-cancelled", jobType))
		if err != nil {
			log.Printf("Failed to track job cancellation: %v", err)
		}
		log.Printf("Job #%d cancelled due to context deadline", id)
		return
	}
}

func main() {
	processor := NewJobProcessor()
	
	// Create a context with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	
	// Process a batch of jobs
	jobs := []string{"email", "report", "backup", "cleanup"}
	processor.ProcessJobs(ctx, jobs)
	
	log.Println("All jobs processed")
}

This example demonstrates:

  1. Usage of contexts for proper cancellation and timeout handling
  2. Thread-safe concurrent operations
  3. Tracking multiple event types (starts, completions, cancellations)
  4. Structured error handling

Best Practices for Go Implementation

When using CounterAPI in your Go applications, consider these best practices:

  1. Embrace Go's Context Pattern:

    func APIHandler(w http.ResponseWriter, r *http.Request) {
      // Use the request's context with a short timeout for analytics
      ctx, cancel := context.WithTimeout(r.Context(), 500*time.Millisecond)
      defer cancel()
    
      counter.Up(ctx, "api-call")
    }
    
  2. Use Background Processing for Analytics:

    func TrackEvent(eventName string) {
      go func() {
        ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
        defer cancel()
    
        _, err := counter.Up(ctx, eventName)
        if err != nil {
          log.Printf("Analytics error: %v", err)
        }
      }()
    }
    
  3. Implement Graceful Error Handling:

    func SafeTrackEvent(eventName string) {
      ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
      defer cancel()
    
      _, err := counter.Up(ctx, eventName)
      if err != nil {
        // Log but don't panic - analytics shouldn't crash your app
        log.Printf("Analytics error for %s: %v", eventName, err)
    
        // Optionally implement fallback or retry logic
      }
    }
    
  4. Use Descriptive Counter Names:

    // Use structured counter names
    counter.Up(ctx, "service:auth:login:success")
    counter.Up(ctx, "service:auth:login:failed")
    counter.Up(ctx, "service:posts:created")
    
  5. Configure from Environment:

    func NewCounterFromEnv() *client.Client {
      return client.New(client.Options{
        Version:    client.V2,
        Workspace:  os.Getenv("COUNTER_WORKSPACE"),
        Debug:      os.Getenv("DEBUG") == "true",
        AccessToken: os.Getenv("COUNTER_API_TOKEN"),
      })
    }
    

Conclusion

CounterAPI's Go client brings powerful, native analytics capabilities to Go applications with minimal overhead. Its thread-safe design, context support, and simple API make it an excellent choice for tracking metrics in Go applications of any size.

Whether you're building web servers, CLI tools, or complex distributed systems, adding meaningful analytics has never been easier. The native Go client aligns perfectly with Go's philosophy of simplicity and performance, allowing you to focus on building your application while still gathering valuable insights.

For more information and advanced usage examples, check out the official CounterAPI Go documentation.

Ready to add analytics to your Go application? Get started with CounterAPI today!