Skip to content

klimeapp/klime-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

klime-go

Official Klime SDK for Go.

Installation

go get github.com/klimeapp/klime-go

Quick Start

package main

import (
    "log"

    klime "github.com/klimeapp/klime-go"
)

func main() {
    client, err := klime.NewClient(klime.Config{
        WriteKey: "your-write-key",
    })
    if err != nil {
        log.Fatal(err)
    }
    defer client.Shutdown()

    // Identify a user
    client.Identify("user_123", map[string]interface{}{
        "email": "user@example.com",
        "name":  "Stefan",
    })

    // Track an event
    client.Track("Button Clicked", map[string]interface{}{
        "button": "signup",
        "plan":   "pro",
    }, klime.WithUserID("user_123"))

    // Associate user with a group and set group traits
    client.Group("org_456", map[string]interface{}{
        "name": "Acme Inc",
        "plan": "enterprise",
    }, klime.WithGroupUserID("user_123"))
}

Installation Prompt

Copy and paste this prompt into Cursor, Copilot, or your favorite AI editor to integrate Klime:

Integrate Klime for customer analytics. Klime tracks user activity to identify which customers are healthy vs at risk of churning.

ANALYTICS MODES (determine which applies):
- Companies & Teams: Your customers are companies with multiple team members (SaaS, enterprise tools)
  → Use Identify() + Group() + Track()
- Individual Customers: Your customers are individuals with private accounts (consumer apps, creator tools)
  → Use Identify() + Track() only (no Group() needed)

KEY CONCEPTS:
- Every Track() call requires either userId OR groupId (no anonymous events)
- Use groupId alone for org-level events (webhooks, cron jobs, system metrics)
- Group() links a user to a company AND sets company traits (only for Companies & Teams mode)
- Order doesn't matter - events before Identify/Group still get attributed correctly

BEST PRACTICES:
- Initialize client ONCE at app startup (singleton pattern)
- Store write key in KLIME_WRITE_KEY environment variable
- Call Shutdown() on process exit to flush remaining events

Install: go get github.com/klimeapp/klime-go

import klime "github.com/klimeapp/klime-go"

client, _ := klime.NewClient(klime.Config{WriteKey: os.Getenv("KLIME_WRITE_KEY")})
defer client.Shutdown()

// Identify users at signup/login:
client.Identify("usr_abc123", map[string]interface{}{"email": "jane@acme.com", "name": "Jane Smith"})

// Track key activities:
client.Track("Report Generated", map[string]interface{}{"report_type": "revenue"}, klime.WithUserID("usr_abc123"))
client.Track("Feature Used", map[string]interface{}{"feature": "export", "format": "csv"}, klime.WithUserID("usr_abc123"))
client.Track("Teammate Invited", map[string]interface{}{"role": "member"}, klime.WithUserID("usr_abc123"))

// If Companies & Teams mode: link user to their company and set company traits
client.Group("org_456", map[string]interface{}{"name": "Acme Inc", "plan": "enterprise"}, klime.WithGroupUserID("usr_abc123"))

INTEGRATION WORKFLOW:

Phase 1: Discover
Explore the codebase to understand:
1. What framework is used? (Gin, Echo, Chi, Fiber, net/http, etc.)
2. Where is user identity available? (e.g., ctx.Value("user"), c.Get("user"), session.UserID, JWT claims)
3. Is this Companies & Teams or Individual Customers?
   - Look for: organization, workspace, tenant, team, account models → Companies & Teams (use Group())
   - No company/org concept, just individual users → Individual Customers (skip Group())
4. Where do core user actions happen? (HTTP handlers, service methods, controllers)
5. Is there existing analytics? (search: segment, posthog, mixpanel, amplitude, Track)
Match your integration style to the framework's conventions.

Phase 2: Instrument
Add these calls using idiomatic patterns for the framework:
- Initialize client once at startup (main.go or init function, use defer Shutdown())
- Identify() in auth/login success handler
- Group() when user-org association is established (Companies & Teams mode only)
- Track() for key user actions (see below)

WHAT TO TRACK:
Active engagement (primary): feature usage, resource creation, collaboration, completing flows
Session signals (secondary): login/session start, dashboard access - distinguishes "low usage" from "churned"
Do NOT track: every HTTP request, health checks, middleware passthrough, background jobs

Phase 3: Verify
Confirm: client initialized, shutdown handled, Identify/Group/Track calls added

Phase 4: Summarize
Report what you added:
- Files modified and what was added to each
- Events being tracked (list event names and what triggers them)
- How userId is obtained (and groupId if Companies & Teams mode)
- Any assumptions made or questions

API Reference

Constructor

client, err := klime.NewClient(klime.Config{
    WriteKey:          "your-write-key",        // Required: Your Klime write key
    Endpoint:          "https://i.klime.com",   // Optional: API endpoint
    FlushInterval:     2 * time.Second,         // Optional: Interval between flushes
    MaxBatchSize:      20,                      // Optional: Max events per batch (max: 100)
    MaxQueueSize:      1000,                    // Optional: Max queued events
    RetryMaxAttempts:  5,                       // Optional: Max retry attempts
    RetryInitialDelay: 1 * time.Second,         // Optional: Initial retry delay
    FlushOnShutdown:   boolPtr(true),           // Optional: Auto-flush on shutdown
    HTTPClient:        &http.Client{},          // Optional: Custom HTTP client
    Logger:            slog.Default(),          // Optional: Structured logger (log/slog)
    OnError:           func(err error, events []*klime.Event) { ... },   // Optional: Error callback
    OnSuccess:         func(response *klime.BatchResponse) { ... },      // Optional: Success callback
})

Methods

Track(event string, properties map[string]interface{}, opts ...TrackOption)

Track a user event. Use WithUserID() to specify the user.

// Basic tracking
client.Track("Button Clicked", map[string]interface{}{
    "button": "signup",
    "plan":   "pro",
}, klime.WithUserID("user_123"))

// With group context
client.Track("Feature Used", map[string]interface{}{
    "feature": "export",
}, klime.WithUserID("user_123"), klime.WithGroupID("org_456"))

Identify(userID string, traits map[string]interface{}, opts ...IdentifyOption)

Identify a user with traits.

client.Identify("user_123", map[string]interface{}{
    "email": "user@example.com",
    "name":  "Stefan",
    "plan":  "pro",
})

Group(groupID string, traits map[string]interface{}, opts ...GroupOption)

Associate a user with a group and/or set group traits.

// Associate user with a group and set group traits
client.Group("org_456", map[string]interface{}{
    "name": "Acme Inc",
    "plan": "enterprise",
}, klime.WithGroupUserID("user_123"))

// Just link a user to a group
client.Group("org_456", nil, klime.WithGroupUserID("user_123"))

// Just update group traits
client.Group("org_456", map[string]interface{}{
    "plan":          "enterprise",
    "employeeCount": 50,
})

Flush()

Manually flush queued events immediately.

client.Flush()

Shutdown()

Gracefully shutdown the client, flushing remaining events.

client.Shutdown()

QueueSize() int

Return the number of events currently in the queue.

pending := client.QueueSize()
fmt.Printf("%d events waiting to be sent\n", pending)

Synchronous Methods

For cases where you need confirmation that events were sent (e.g., before process exit, in tests), use the synchronous variants:

TrackSync(ctx, event, properties, opts...) (*BatchResponse, error)

Track an event synchronously. Returns *BatchResponse or error.

ctx := context.Background()
response, err := client.TrackSync(ctx, "Critical Action", map[string]interface{}{
    "key": "value",
}, klime.WithUserID("user_123"))

if err != nil {
    if sendErr, ok := err.(*klime.SendError); ok {
        fmt.Printf("Failed to send: %s\n", sendErr.Message)
    }
}
fmt.Printf("Sent! Accepted: %d\n", response.Accepted)

IdentifySync(ctx, userID, traits, opts...) (*BatchResponse, error)

Identify a user synchronously. Returns *BatchResponse or error.

GroupSync(ctx, groupID, traits, opts...) (*BatchResponse, error)

Associate a user with a group synchronously. Returns *BatchResponse or error.

Features

  • Automatic Batching: Events are automatically batched and sent every 2 seconds or when the batch reaches 20 events
  • Automatic Retries: Failed requests are automatically retried with exponential backoff
  • Thread-Safe: Safe to use from multiple goroutines
  • Process Exit Handling: Automatically flushes events on SIGINT/SIGTERM
  • Zero Dependencies: Uses only Go standard library

Performance

When you call Track(), Identify(), or Group(), the SDK:

  1. Adds the event to an in-memory queue protected by a mutex (microseconds)
  2. Returns immediately without waiting for network I/O

Events are sent to Klime's servers in background goroutines. This design means:

  • No network blocking: HTTP requests happen asynchronously in background goroutines
  • No latency impact: Tracking calls add < 1ms to your request handling time
  • Automatic batching: Events are queued and sent in batches (default: every 2 seconds or 20 events)
// This returns immediately - no HTTP request is made here
client.Track("Button Clicked", map[string]interface{}{
    "button": "signup",
}, klime.WithUserID("user_123"))

// Your code continues without waiting
json.NewEncoder(w).Encode(map[string]bool{"success": true})

The only blocking operation is Flush(), which waits for all queued events to be sent. This is typically only called during graceful shutdown.

Configuration

Default Values

Setting Default
FlushInterval 2 seconds
MaxBatchSize 20 events
MaxQueueSize 1000 events
RetryMaxAttempts 5 attempts
RetryInitialDelay 1 second
FlushOnShutdown true

Logging

The SDK supports Go's built-in log/slog (Go 1.21+). Pass a logger to enable structured logging:

import "log/slog"

client, _ := klime.NewClient(klime.Config{
    WriteKey: "your-write-key",
    Logger:   slog.Default(), // Use default logger
})

// Or with a custom logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))
client, _ := klime.NewClient(klime.Config{
    WriteKey: "your-write-key",
    Logger:   logger,
})

If Logger is nil (default), no logging is performed.

Callbacks

client, _ := klime.NewClient(klime.Config{
    WriteKey: "your-write-key",
    OnError: func(err error, events []*klime.Event) {
        // Report to your error tracking service
        sentry.CaptureException(err)
        log.Printf("Failed to send %d events: %v", len(events), err)
    },
    OnSuccess: func(response *klime.BatchResponse) {
        log.Printf("Sent %d events", response.Accepted)
    },
})

Error Handling

The SDK automatically handles:

  • Transient errors (429, 503, network failures): Retries with exponential backoff
  • Permanent errors (400, 401): Logs error and drops batch
  • Rate limiting: Respects Retry-After header

For synchronous operations, use *Sync() methods which return *SendError on failure:

response, err := client.TrackSync(ctx, "Event", nil, klime.WithUserID("user_123"))
if err != nil {
    if sendErr, ok := err.(*klime.SendError); ok {
        fmt.Printf("Failed: %s, events: %d\n", sendErr.Message, len(sendErr.Events))
    }
}

Size Limits

  • Maximum event size: 200KB
  • Maximum batch size: 10MB
  • Maximum events per batch: 100

Events exceeding these limits are rejected and logged.

HTTP Server Example

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"

    klime "github.com/klimeapp/klime-go"
)

var client *klime.Client

func main() {
    var err error
    client, err = klime.NewClient(klime.Config{
        WriteKey: os.Getenv("KLIME_WRITE_KEY"),
    })
    if err != nil {
        log.Fatal(err)
    }

    // Graceful shutdown
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
    go func() {
        <-sigChan
        client.Shutdown()
        os.Exit(0)
    }()

    http.HandleFunc("/api/button-clicked", buttonClickedHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func buttonClickedHandler(w http.ResponseWriter, r *http.Request) {
    var req struct {
        ButtonName string `json:"buttonName"`
        UserID     string `json:"userId"`
    }
    json.NewDecoder(r.Body).Decode(&req)

    client.Track("Button Clicked", map[string]interface{}{
        "buttonName": req.ButtonName,
    }, klime.WithUserID(req.UserID))

    json.NewEncoder(w).Encode(map[string]bool{"success": true})
}

Gin Example

package main

import (
    "log"
    "os"

    "github.com/gin-gonic/gin"
    klime "github.com/klimeapp/klime-go"
)

var client *klime.Client

func main() {
    var err error
    client, err = klime.NewClient(klime.Config{
        WriteKey: os.Getenv("KLIME_WRITE_KEY"),
    })
    if err != nil {
        log.Fatal(err)
    }
    defer client.Shutdown()

    r := gin.Default()

    r.POST("/api/button-clicked", func(c *gin.Context) {
        var req struct {
            ButtonName string `json:"buttonName"`
            UserID     string `json:"userId"`
        }
        c.BindJSON(&req)

        client.Track("Button Clicked", map[string]interface{}{
            "buttonName": req.ButtonName,
        }, klime.WithUserID(req.UserID))

        c.JSON(200, gin.H{"success": true})
    })

    r.Run(":8080")
}

Requirements

  • Go 1.21 or higher
  • No external dependencies (uses only standard library)

License

MIT

About

Klime SDK for Go

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages