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.
Tutorial Expert
• 6 min read
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:
- Creates a global counter client safe for concurrent use
- Uses middleware to track all API requests by endpoint
- Processes analytics asynchronously to avoid impacting response times
- 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:
- Integrates CounterAPI with a Cobra-based CLI application
- Tracks each command execution via PersistentPreRun
- Provides a usage command to display analytics
- 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:
- Usage of contexts for proper cancellation and timeout handling
- Thread-safe concurrent operations
- Tracking multiple event types (starts, completions, cancellations)
- Structured error handling
Best Practices for Go Implementation
When using CounterAPI in your Go applications, consider these best practices:
-
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") }
-
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) } }() }
-
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 } }
-
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")
-
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!