A high-performance, scalable, and fault-tolerant event tracking Go SDK for server-side applications.
- Zero Runtime Dependencies – Built entirely with Go standard library
- Thread-Safe – Concurrent event tracking with mutex protection
- Auto-Initialization –
Track()automatically callsInit()if not yet initialized - Disposal Tracking – Disposed clients silently drop events; explicit
Init()re-enables - Automatic Batching – Efficient event grouping with dynamic rebatching for optimal network usage
- One-Shot Timer – Flush timer fires once per scheduling cycle, not on a repeating interval
- Smart Retry Logic – Intelligent retry behavior based on HTTP status codes:
- 2xx (Success): Clear storage, no retry
- 4xx (Client Error): Drop events, no retry (prevents infinite loops)
- 5xx (Server Error): Retry with exponential backoff, re-queue on max retries
- Network Errors: Retry with exponential backoff, re-queue on max retries
- Retry Cancellation –
Dispose()aborts in-flight retries via context cancellation - Event Persistence – Disk-backed storage for reliability
- Pluggable Adapters – Custom HTTP and storage implementations
go get github.com/Tap30/ripple-go@latestOr install a specific version:
go get github.com/Tap30/ripple-go@v0.0.1package main
import (
ripple "github.com/Tap30/ripple-go"
"github.com/Tap30/ripple-go/adapters"
)
func main() {
client, err := ripple.NewClient(ripple.ClientConfig{
APIKey: "your-api-key",
Endpoint: "https://api.example.com/events",
HTTPAdapter: adapters.NewNetHTTPAdapter(),
StorageAdapter: adapters.NewNoOpStorageAdapter(),
})
if err != nil {
panic(err)
}
defer client.Dispose()
// Set global metadata
client.SetMetadata("userId", "123")
client.SetMetadata("appVersion", "1.0.0")
// Track events (auto-initializes on first call)
client.Track("page_view", map[string]any{
"page": "/home",
}, nil)
// Track with event-specific metadata
client.Track("user_action", map[string]any{
"button": "submit",
}, map[string]any{
"schemaVersion": "1.0.0",
})
// Manually flush
client.Flush()
}type ClientConfig struct {
APIKey string // Required: API authentication key
Endpoint string // Required: Event collection endpoint
APIKeyHeader *string // Optional: Header name for API key (default: "X-API-Key")
FlushInterval time.Duration // Optional: Default 5s
MaxBatchSize int // Optional: Default 10
MaxRetries int // Optional: Default 3
MaxBufferSize int // Optional: Max events in storage (0 = unlimited)
HTTPAdapter HTTPAdapter // Required: Custom HTTP adapter
StorageAdapter StorageAdapter // Required: Custom storage adapter
LoggerAdapter LoggerAdapter // Optional: Custom logger adapter
}Configuration validation:
FlushIntervalmust be positive if providedMaxBatchSizemust be positive if providedMaxRetriesmust be non-negative if providedMaxBufferSizemust be positive if provided, and >=MaxBatchSize
MaxBatchSize (default: 10) - Controls when events are sent
- Triggers immediate flush when queue reaches this size
- Determines how many events are sent in each HTTP request
MaxBufferSize (default: 0 = unlimited) - Controls how many events are stored
- Limits total events persisted to storage
- When limit is reached, oldest events are dropped (FIFO eviction)
- Must be >=
MaxBatchSize(returns error otherwise)
Initializes the client and restores persisted events. Uses double-checked locking for thread safety. Resets the disposed state, so calling Init() after Dispose() re-enables the client.
Note: Track() automatically calls Init(), so explicit initialization is optional.
Tracks an event with optional payload and metadata.
Parameters:
name- Event name/identifier (required, cannot be empty)payload- Event data payload (optional, passnilif not needed)metadata- Event-specific metadata (optional, passnilif not needed)
Usage examples:
Track("page_view", nil, nil)- Simple event trackingTrack("click", map[string]any{"button": "submit"}, nil)- Event with payloadTrack("purchase", payload, map[string]any{"version": "1.0"})- Event with payload and metadata
If the client is disposed, events are silently dropped (returns nil). Otherwise, auto-calls Init() if not yet initialized.
Sets a metadata value that will be attached to all subsequent events.
Returns a copy of all stored metadata. Returns empty map if no metadata is set.
Returns nil for server environments.
Manually triggers a flush of all queued events.
Cleans up resources: aborts in-flight retries, clears queue, clears metadata, resets state. Does NOT flush events. Call Flush() before Dispose() if you want to send remaining events.
Alias for Dispose().
Implement the HTTPAdapter interface to use custom HTTP clients:
import (
"context"
"github.com/Tap30/ripple-go/adapters"
)
type MyHTTPAdapter struct{}
func (a *MyHTTPAdapter) Send(endpoint string, events []adapters.Event, headers map[string]string) (*adapters.HTTPResponse, error) {
return a.SendWithContext(context.Background(), endpoint, events, headers)
}
func (a *MyHTTPAdapter) SendWithContext(ctx context.Context, endpoint string, events []adapters.Event, headers map[string]string) (*adapters.HTTPResponse, error) {
// custom HTTP logic
return &adapters.HTTPResponse{Status: 200}, nil
}import "github.com/Tap30/ripple-go/adapters"
type RedisStorage struct{}
func (r *RedisStorage) Save(events []adapters.Event) error { return nil }
func (r *RedisStorage) Load() ([]adapters.Event, error) { return nil, nil }
func (r *RedisStorage) Clear() error { return nil }sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-sigChan
client.Flush()
client.Dispose()
os.Exit(0)
}()| Adapter | Output | Configurable | Use Case |
|---|---|---|---|
| PrintLoggerAdapter | Stdout | Yes | Development and debugging |
| NoOpLoggerAdapter | None | No | Production (silent logging) |
DEBUG: Detailed debugging informationINFO: General information messagesWARN: Warning messages (default level)ERROR: Error messagesNONE: No logging output
import "github.com/Tap30/ripple-go/adapters"
client, err := ripple.NewClient(ripple.ClientConfig{
// ... other config
LoggerAdapter: adapters.NewPrintLoggerAdapter(adapters.LogLevelDebug),
})| Adapter | Capacity | Persistence | Use Case |
|---|---|---|---|
| NoOpStorageAdapter | N/A | None | Default, no persistence |
| Adapter | Capacity | Persistence | Use Case |
| ---------------------- | --------- | ----------- | --------------------------------- |
| NoOpStorageAdapter | N/A | None | Default, no persistence |
import "github.com/Tap30/ripple-go/adapters"
// No persistence (default)
storage := adapters.NewNoOpStorageAdapter()For custom storage implementations (e.g., file, Redis, database), implement the StorageAdapter interface. See adapters/README.md for examples.
- Thread-Safe Flush: Multiple concurrent
Flush()calls are serialized via mutex - Thread-Safe Init: Double-checked locking prevents race conditions during auto-init
- Event Ordering: FIFO order is maintained even during retry failures
- No Event Loss: Events tracked during flush are queued for the next batch
- 2xx Success: Events cleared from storage
- 4xx Client Errors: Events dropped (no retry)
- 5xx Server Errors: Retried with exponential backoff (30s cap), re-queued on max retries
- Network Errors: Same as 5xx
- Client – Public API, metadata management, disposal tracking
- Dispatcher – Event batching, one-shot timer flushing, retry with context cancellation
- Queue – Thread-safe FIFO event queue
- MetadataManager – Thread-safe shared metadata
- Adapters – Pluggable HTTP, storage, and logger implementations
See AGENTS.md for detailed architecture documentation.
make test # Run all tests
make test-cover # Run tests with coverage
make fmt # Format code
make lint # Run linter
make build # Build all packages
make check # Run all CI checks# Terminal 1: Start server
cd playground && make server
# Terminal 2: Run client
cd playground && make clientRead the Design and API Contract Documentation to learn about the framework-agnostic API contract for SDKs.
See the contributing guide.
Uses Conventional Commits and automated semantic versioning.
Distributed under the MIT license.
