Project

klime

0.0
A long-lived project that still receives updates
Track events, identify users, and group them with organizations using Klime analytics.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 5.0
~> 13.0
~> 3.0

Runtime

>= 0
 Project Readme

klime

Klime SDK for Ruby.

Installation

Add this line to your application's Gemfile:

gem 'klime'

And then execute:

bundle install

Or install it yourself as:

gem install klime

Quick Start

require 'klime'

client = Klime::Client.new(
  write_key: 'your-write-key'
)

# Identify a user
client.identify('user_123', {
  email: 'user@example.com',
  name: 'Stefan'
})

# Track an event
client.track('Button Clicked', {
  button_name: 'Sign up',
  plan: 'pro'
}, user_id: 'user_123')

# Associate user with a group and set group traits
client.group('org_456', {
  name: 'Acme Inc',
  plan: 'enterprise'
}, user_id: 'user_123')

# Or just link the user to a group (if traits are already set)
client.group('org_456', user_id: 'user_123')

# Shutdown gracefully
client.shutdown

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 user_id OR group_id (no anonymous events)
- Use group_id 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 (initializer or singleton)
- Store write key in KLIME_WRITE_KEY environment variable
- Call shutdown on process exit to flush remaining events (auto-registered via at_exit)

Add to Gemfile: gem 'klime'
Then run: bundle install

Or install directly: gem install klime

require 'klime'

client = Klime::Client.new(write_key: ENV['KLIME_WRITE_KEY'])

# Identify users at signup/login:
client.identify('usr_abc123', { email: 'jane@acme.com', name: 'Jane Smith' })

# Track key activities:
client.track('Report Generated', { report_type: 'revenue' }, user_id: 'usr_abc123')
client.track('Feature Used', { feature: 'export', format: 'csv' }, user_id: 'usr_abc123')
client.track('Teammate Invited', { role: 'member' }, user_id: 'usr_abc123')

# If Companies & Teams mode: link user to their company and set company traits
client.group('org_456', { name: 'Acme Inc', plan: 'enterprise' }, user_id: 'usr_abc123')

INTEGRATION WORKFLOW:

Phase 1: Discover
Explore the codebase to understand:
1. What framework is used? (Rails, Sinatra, Hanami, Rack, etc.)
2. Where is user identity available? (e.g., current_user.id, @current_user.id, session[:user_id], warden.user)
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? (controllers, service objects, jobs, callbacks)
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 (Rails: config/initializers/klime.rb, Sinatra: before app.run, Rack: middleware)
- 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 request, health checks, before_action filters, 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 user_id is obtained (and group_id if Companies & Teams mode)
- Any assumptions made or questions

API Reference

Constructor

Klime::Client.new(
  write_key:,              # Required: Your Klime write key
  endpoint: nil,           # Optional: API endpoint (default: https://i.klime.com)
  flush_interval: nil,     # Optional: Milliseconds between flushes (default: 2000)
  max_batch_size: nil,     # Optional: Max events per batch (default: 20, max: 100)
  max_queue_size: nil,     # Optional: Max queued events (default: 1000)
  retry_max_attempts: nil, # Optional: Max retry attempts (default: 5)
  retry_initial_delay: nil, # Optional: Initial retry delay in ms (default: 1000)
  flush_on_shutdown: nil,  # Optional: Auto-flush on exit (default: true)
  on_error: nil,           # Optional: Callback for batch failures
  on_success: nil          # Optional: Callback for successful sends
)

Methods

track(event_name, properties = nil, user_id: nil, group_id: nil)

Track an event. Events can be attributed in two ways:

  • User events: Provide user_id to track user activity (most common)
  • Group events: Provide group_id without user_id for organization-level events
# User event (most common)
client.track('Button Clicked', {
  button_name: 'Sign up',
  plan: 'pro'
}, user_id: 'user_123')

# Group event (for webhooks, cron jobs, system events)
client.track('Events Received', {
  count: 100,
  source: 'webhook'
}, group_id: 'org_456')

Note: The group_id parameter can also be combined with user_id for multi-tenant scenarios where you need to specify which organization context a user event occurred in.

identify(user_id, traits = nil)

Identify a user with traits.

client.identify('user_123', {
  email: 'user@example.com',
  name: 'Stefan'
})

group(group_id, traits = nil, user_id: nil)

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

# Associate user with a group and set group traits (most common)
client.group('org_456', {
  name: 'Acme Inc',
  plan: 'enterprise'
}, user_id: 'user_123')

# Just link a user to a group (traits already set or not needed)
client.group('org_456', user_id: 'user_123')

# Just update group traits (e.g., from a webhook or background job)
client.group('org_456', {
  plan: 'enterprise',
  employee_count: 50
})

flush

Manually flush queued events immediately.

client.flush

shutdown

Gracefully shutdown the client, flushing remaining events.

client.shutdown

Features

  • Automatic Batching: Events are automatically batched and sent every 2 seconds or when the batch size reaches 20 events
  • Automatic Retries: Failed requests are automatically retried with exponential backoff
  • Thread-Safe: Safe to use from multiple threads
  • Fork-Safe: Automatically detects Puma/Unicorn forks and restarts the worker thread
  • Process Exit Handling: Automatically flushes events on process exit (via at_exit)
  • Zero Dependencies: Uses only Ruby standard library

Performance

When you call track(), identify(), or group(), the SDK:

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

Events are sent to Klime's servers in a background thread. This means:

  • No network blocking: HTTP requests happen asynchronously in a background thread
  • 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', { button: 'signup' }, user_id: 'user_123')

# Your code continues without waiting
render json: { 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

  • flush_interval: 2000ms
  • max_batch_size: 20 events
  • max_queue_size: 1000 events
  • retry_max_attempts: 5 attempts
  • retry_initial_delay: 1000ms
  • flush_on_shutdown: true

Logging

Klime.configure do |config|
  config.logger = Rails.logger
end

Callbacks

Klime.configure do |config|
  config.on_error = Proc.new { |error, batch|
    Sentry.capture_exception(error)
  }

  config.on_success = Proc.new { |response|
    Rails.logger.info "Sent #{response.accepted} events"
  }
end

Error Handling

The SDK automatically handles:

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

For synchronous operations, use bang methods (track!, identify!, group!) which raise Klime::SendError on failure.

Size Limits

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

Events exceeding these limits are rejected and logged.

Rails Example

# config/initializers/klime.rb
require 'klime'

Klime.configure do |config|
  config.logger = Rails.logger
end

Klime.client = Klime::Client.new(write_key: ENV['KLIME_WRITE_KEY'])
KLIME = Klime.client
# app/controllers/buttons_controller.rb
class ButtonsController < ApplicationController
  def click
    KLIME.track('Button Clicked', {
      button_name: params[:button_name]
    }, user_id: current_user&.id&.to_s)

    render json: { success: true }
  end
end

For Puma with multiple workers, add to config/puma.rb:

on_worker_boot do
  Klime.restart!
end

Sinatra Example

require 'sinatra'
require 'klime'

client = Klime::Client.new(write_key: ENV['KLIME_WRITE_KEY'])

post '/api/button-clicked' do
  data = JSON.parse(request.body.read)

  client.track('Button Clicked', {
    button_name: data['buttonName']
  }, user_id: data['userId'])

  { success: true }.to_json
end

# Graceful shutdown
at_exit { client.shutdown }

Requirements

  • Ruby 2.6 or higher
  • No external dependencies (uses only standard library)

License

MIT