Skip to main content
The Go SDK sends events and structured logs from your backend services to Tell. It’s a server SDK — you create a client, pass a user ID on every call, and a background goroutine handles batching and delivery over TCP. Every method takes a context.Context and returns an error. The calling goroutine does about 320 ns of work (serialize, encode, enqueue) and never touches the network.

Installation

go get github.com/tell-rs/tell-go
Requires Go 1.24+.

Quick start

package main

import (
    "context"

    "github.com/tell-rs/tell-go"
)

func main() {
    client, err := tell.Production("a1b2c3d4e5f60718293a4b5c6d7e8f90")
    if err != nil {
        panic(err)
    }
    defer client.Close(context.Background())

    client.Track(context.Background(), "user_123", "Page Viewed", tell.Properties{
        "url":      "/home",
        "referrer": "google",
    })
}
The client connects to collect.tell.rs:50000, batches up to 100 events, and flushes every 10 seconds or when you call Close.

Configuration

Use one of the two presets, or pass a custom Config struct.
// Production — collect.tell.rs:50000, batch 100, flush 10s
client, err := tell.Production("a1b2c3d4e5f60718293a4b5c6d7e8f90")

// Development — localhost:50000, batch 10, flush 2s
client, err := tell.Development("a1b2c3d4e5f60718293a4b5c6d7e8f90")
Both presets accept an optional error callback:
client, err := tell.Development("a1b2c3d4e5f60718293a4b5c6d7e8f90",
    func(e error) { fmt.Println("[Tell]", e) },
)
For custom settings:
client, err := tell.NewClient("a1b2c3d4e5f60718293a4b5c6d7e8f90", tell.Config{
    Endpoint:       "collect.internal:50000",
    BatchSize:      200,
    FlushInterval:  5 * time.Second,
    MaxRetries:     5,
    CloseTimeout:   10 * time.Second,
    NetworkTimeout: 60 * time.Second,
    OnError:        func(e error) { fmt.Println("[Tell]", e) },
})
The API key must be a 32-character hex string. NewClient returns an error if it’s invalid.

Tracking events

Every tracking method takes context.Context and userID as its first parameters. Properties are tell.Properties (map[string]interface{}) or nil.
ctx := context.Background()

// Track a custom event
client.Track(ctx, "user_123", "Feature Used", tell.Properties{
    "feature": "dark_mode",
    "enabled": true,
})

// Identify a user
client.Identify(ctx, "user_123", tell.Properties{
    "name":  "Jane",
    "email": "[email protected]",
    "plan":  "pro",
})

// Associate user with a group
client.Group(ctx, "user_123", "org_456", tell.Properties{
    "name": "Acme Corp",
    "plan": "enterprise",
})

// Track revenue
client.Revenue(ctx, "user_123", 49.99, "USD", "order_789", tell.Properties{
    "product": "pro_annual",
})

// Link two user identities
client.Alias(ctx, "anon_visitor_abc", "user_123")
Pass nil instead of tell.Properties{} when you don’t need properties:
client.Track(ctx, "user_123", "App Opened", nil)
client.Revenue(ctx, "user_123", 9.99, "USD", "order_790", nil)

Standard event names

The SDK provides constants for common events:
client.Track(ctx, "user_123", tell.PageViewed, tell.Properties{"url": "/pricing"})
client.Track(ctx, "user_123", tell.UserSignedUp, tell.Properties{"source": "organic"})
client.Track(ctx, "user_123", tell.OrderCompleted, tell.Properties{"total": 99.00})
client.Track(ctx, "user_123", tell.FeatureUsed, tell.Properties{"feature": "export"})
Constants cover user lifecycle, revenue, subscriptions, trials, shopping, engagement, and communication events. Custom string names are always accepted too.

Super properties

Register properties that get merged into every Track, Group, and Revenue call:
client.Register(tell.Properties{"app_version": "2.1.0", "env": "production"})

// This Track call automatically includes app_version and env
client.Track(ctx, "user_123", "Click", tell.Properties{"button": "submit"})

// Remove a super property
client.Unregister("env")
Event-specific properties override super properties when keys conflict.

Structured logging

Send logs alongside events through the same pipeline. Each log has an RFC 5424 severity level.
client.LogError(ctx, "DB connection failed", tell.Ptr("api"), map[string]interface{}{
    "host":    "db.internal",
    "retries": 3,
})

client.LogInfo(ctx, "Request processed", tell.Ptr("api"), map[string]interface{}{
    "status":      200,
    "duration_ms": 45,
})

client.LogWarning(ctx, "Rate limit approaching", tell.Ptr("gateway"), nil)
The service parameter is *string — use tell.Ptr("myservice") to pass a value, or nil to default to "app". Convenience methods are available for all nine levels: LogEmergency, LogAlert, LogCritical, LogError, LogWarning, LogNotice, LogInfo, LogDebug, and LogTrace. For full control, use the Log method with a LogEntry struct:
client.Log(ctx, tell.LogEntry{
    Level:   tell.LogNoticeLevel,
    Message: "Deployment completed",
    Service: "deploy",
    Data: map[string]interface{}{
        "version": "3.1.0",
        "commit":  "abc123f",
    },
})

Properties

tell.Properties is map[string]interface{}. Supported value types:
  • string
  • int, int32, int64, float32, float64
  • bool
  • time.Time
  • []interface{} (arrays)
  • map[string]interface{} (nested objects)
  • nil

Lifecycle

ctx := context.Background()

// Force-send all queued events and logs
client.Flush(ctx)

// Rotate session ID
client.ResetSession()

// Flush + shut down the background goroutine
client.Close(ctx)
Always call Close before your process exits to avoid losing buffered events. Use defer client.Close(ctx) right after creating the client.

Goroutine safety

The client is safe to share across goroutines:
client, _ := tell.Production("a1b2c3d4e5f60718293a4b5c6d7e8f90")

go func() { client.Track(ctx, "user_1", "Event A", nil) }()
go func() { client.Track(ctx, "user_2", "Event B", nil) }()
go func() { client.LogInfo(ctx, "Processing", tell.Ptr("worker"), nil) }()

Error handling

Constructor errors are returned directly:
client, err := tell.Production("bad-key")
if err != nil {
    // Invalid API key, bad endpoint, etc.
    log.Fatal(err)
}
Tracking and logging methods return validation errors for invalid input. Network errors are handled internally with retries and reported via the OnError callback:
client, _ := tell.NewClient("a1b2c3d4e5f60718293a4b5c6d7e8f90", tell.Config{
    OnError: func(e error) {
        var ve *types.ValidationError
        if errors.As(e, &ve) {
            log.Printf("Validation: %s - %s", ve.Field, ve.Message)
        } else {
            log.Printf("Tell error: %v", e)
        }
    },
})

Advanced

Configuration reference

ParameterDefault (production)Default (development)Description
Endpointcollect.tell.rs:50000localhost:50000TCP collector address
BatchSize10010Events per batch before auto-flush
FlushInterval10s2sTime between auto-flushes
MaxRetries33Retry attempts on send failure
CloseTimeout5s5sMax wait on Close
NetworkTimeout30s30sTCP connect timeout
OnErrorsilentsilentError callback

Validation rules

FieldRule
API key32-character hex string
UserIDNon-empty
Event nameNon-empty
GroupIDNon-empty
Revenue amountPositive number
CurrencyNon-empty
OrderIDNon-empty
Log message or dataAt least one required

Performance

Caller latency — serialize, encode, and enqueue (Apple M4 Pro):
Operationns/opallocs/op
Track (no props)3202
Track (2 props)4942
Track (14 props)1,0612
Identify5112
Revenue5543
Log (with data)4213
Delivery throughput — batched and sent over TCP:
Batch sizeevents/sec
101.2M/s
1001.9M/s
5002.2M/s