Skip to content

Commit 86ab2f5

Browse files
committed
Release v1.1.0
1 parent e92e53d commit 86ab2f5

17 files changed

Lines changed: 1297 additions & 158 deletions

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [1.0.4] - 2025-12-08
8+
## [1.1.0] - 2026-01-26
9+
910

10-
No user-facing changes in this release.
1111

Gemfile.lock

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
PATH
2+
remote: .
3+
specs:
4+
klime (1.0.4)
5+
logger
6+
7+
GEM
8+
remote: https://rubygems.org/
9+
specs:
10+
addressable (2.8.8)
11+
public_suffix (>= 2.0.2, < 8.0)
12+
bigdecimal (4.0.1)
13+
crack (1.0.1)
14+
bigdecimal
15+
rexml
16+
hashdiff (1.2.1)
17+
logger (1.7.0)
18+
minitest (5.27.0)
19+
public_suffix (7.0.2)
20+
rake (13.3.1)
21+
rexml (3.4.4)
22+
webmock (3.26.1)
23+
addressable (>= 2.8.0)
24+
crack (>= 0.3.2)
25+
hashdiff (>= 0.4.0, < 2.0.0)
26+
27+
PLATFORMS
28+
arm64-darwin-25
29+
ruby
30+
31+
DEPENDENCIES
32+
klime!
33+
minitest (~> 5.0)
34+
rake (~> 13.0)
35+
webmock (~> 3.0)
36+
37+
BUNDLED WITH
38+
2.6.9

README.md

Lines changed: 160 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,86 @@ client.group('org_456', user_id: 'user_123')
5656
client.shutdown
5757
```
5858

59+
## Installation Prompt
60+
61+
Copy and paste this prompt into Cursor, Copilot, or your favorite AI editor to integrate Klime:
62+
63+
```
64+
Integrate Klime for customer analytics. Klime tracks user activity to identify which customers are healthy vs at risk of churning.
65+
66+
ANALYTICS MODES (determine which applies):
67+
- Companies & Teams: Your customers are companies with multiple team members (SaaS, enterprise tools)
68+
→ Use identify() + group() + track()
69+
- Individual Customers: Your customers are individuals with private accounts (consumer apps, creator tools)
70+
→ Use identify() + track() only (no group() needed)
71+
72+
KEY CONCEPTS:
73+
- Every track() call requires either user_id OR group_id (no anonymous events)
74+
- Use group_id alone for org-level events (webhooks, cron jobs, system metrics)
75+
- group() links a user to a company AND sets company traits (only for Companies & Teams mode)
76+
- Order doesn't matter - events before identify/group still get attributed correctly
77+
78+
BEST PRACTICES:
79+
- Initialize client ONCE at app startup (initializer or singleton)
80+
- Store write key in KLIME_WRITE_KEY environment variable
81+
- Call shutdown on process exit to flush remaining events (auto-registered via at_exit)
82+
83+
Add to Gemfile: gem 'klime'
84+
Then run: bundle install
85+
86+
Or install directly: gem install klime
87+
88+
require 'klime'
89+
90+
client = Klime::Client.new(write_key: ENV['KLIME_WRITE_KEY'])
91+
92+
# Identify users at signup/login:
93+
client.identify('usr_abc123', { email: '[email protected]', name: 'Jane Smith' })
94+
95+
# Track key activities:
96+
client.track('Report Generated', { report_type: 'revenue' }, user_id: 'usr_abc123')
97+
client.track('Feature Used', { feature: 'export', format: 'csv' }, user_id: 'usr_abc123')
98+
client.track('Teammate Invited', { role: 'member' }, user_id: 'usr_abc123')
99+
100+
# If Companies & Teams mode: link user to their company and set company traits
101+
client.group('org_456', { name: 'Acme Inc', plan: 'enterprise' }, user_id: 'usr_abc123')
102+
103+
INTEGRATION WORKFLOW:
104+
105+
Phase 1: Discover
106+
Explore the codebase to understand:
107+
1. What framework is used? (Rails, Sinatra, Hanami, Rack, etc.)
108+
2. Where is user identity available? (e.g., current_user.id, @current_user.id, session[:user_id], warden.user)
109+
3. Is this Companies & Teams or Individual Customers?
110+
- Look for: organization, workspace, tenant, team, account models → Companies & Teams (use group())
111+
- No company/org concept, just individual users → Individual Customers (skip group())
112+
4. Where do core user actions happen? (controllers, service objects, jobs, callbacks)
113+
5. Is there existing analytics? (search: segment, posthog, mixpanel, amplitude, track)
114+
Match your integration style to the framework's conventions.
115+
116+
Phase 2: Instrument
117+
Add these calls using idiomatic patterns for the framework:
118+
- Initialize client once (Rails: config/initializers/klime.rb, Sinatra: before app.run, Rack: middleware)
119+
- identify() in auth/login success handler
120+
- group() when user-org association is established (Companies & Teams mode only)
121+
- track() for key user actions (see below)
122+
123+
WHAT TO TRACK:
124+
Active engagement (primary): feature usage, resource creation, collaboration, completing flows
125+
Session signals (secondary): login/session start, dashboard access - distinguishes "low usage" from "churned"
126+
Do NOT track: every request, health checks, before_action filters, background jobs
127+
128+
Phase 3: Verify
129+
Confirm: client initialized, shutdown handled, identify/group/track calls added
130+
131+
Phase 4: Summarize
132+
Report what you added:
133+
- Files modified and what was added to each
134+
- Events being tracked (list event names and what triggers them)
135+
- How user_id is obtained (and group_id if Companies & Teams mode)
136+
- Any assumptions made or questions
137+
```
138+
59139
## API Reference
60140

61141
### Constructor
@@ -69,43 +149,48 @@ Klime::Client.new(
69149
max_queue_size: nil, # Optional: Max queued events (default: 1000)
70150
retry_max_attempts: nil, # Optional: Max retry attempts (default: 5)
71151
retry_initial_delay: nil, # Optional: Initial retry delay in ms (default: 1000)
72-
flush_on_shutdown: nil # Optional: Auto-flush on exit (default: true)
152+
flush_on_shutdown: nil, # Optional: Auto-flush on exit (default: true)
153+
on_error: nil, # Optional: Callback for batch failures
154+
on_success: nil # Optional: Callback for successful sends
73155
)
74156
```
75157

76158
### Methods
77159

78-
#### `track(event_name, properties = nil, user_id: nil, group_id: nil, ip: nil)`
160+
#### `track(event_name, properties = nil, user_id: nil, group_id: nil)`
79161

80-
Track a user event. A `user_id` is required for events to be useful in Klime.
162+
Track an event. Events can be attributed in two ways:
163+
- **User events**: Provide `user_id` to track user activity (most common)
164+
- **Group events**: Provide `group_id` without `user_id` for organization-level events
81165

82166
```ruby
167+
# User event (most common)
83168
client.track('Button Clicked', {
84169
button_name: 'Sign up',
85170
plan: 'pro'
86171
}, user_id: 'user_123')
87172

88-
# With IP address (for geolocation)
89-
client.track('Button Clicked', {
90-
button_name: 'Sign up',
91-
plan: 'pro'
92-
}, user_id: 'user_123', ip: '192.168.1.1')
173+
# Group event (for webhooks, cron jobs, system events)
174+
client.track('Events Received', {
175+
count: 100,
176+
source: 'webhook'
177+
}, group_id: 'org_456')
93178
```
94179

95-
> **Advanced**: The `group_id` parameter is available for multi-tenant scenarios where a user belongs to multiple organizations and you need to specify which organization context the event occurred in.
180+
> **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.
96181
97-
#### `identify(user_id, traits = nil, ip: nil)`
182+
#### `identify(user_id, traits = nil)`
98183

99184
Identify a user with traits.
100185

101186
```ruby
102187
client.identify('user_123', {
103188
104189
name: 'Stefan'
105-
}, ip: '192.168.1.1')
190+
})
106191
```
107192

108-
#### `group(group_id, traits = nil, user_id: nil, ip: nil)`
193+
#### `group(group_id, traits = nil, user_id: nil)`
109194

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

@@ -147,9 +232,33 @@ client.shutdown
147232
- **Automatic Batching**: Events are automatically batched and sent every 2 seconds or when the batch size reaches 20 events
148233
- **Automatic Retries**: Failed requests are automatically retried with exponential backoff
149234
- **Thread-Safe**: Safe to use from multiple threads
235+
- **Fork-Safe**: Automatically detects Puma/Unicorn forks and restarts the worker thread
150236
- **Process Exit Handling**: Automatically flushes events on process exit (via `at_exit`)
151237
- **Zero Dependencies**: Uses only Ruby standard library
152238

239+
## Performance
240+
241+
When you call `track()`, `identify()`, or `group()`, the SDK:
242+
243+
1. Adds the event to an in-memory queue (microseconds)
244+
2. Returns immediately without waiting for network I/O
245+
246+
Events are sent to Klime's servers in a background thread. This means:
247+
248+
- **No network blocking**: HTTP requests happen asynchronously in a background thread
249+
- **No latency impact**: Tracking calls add < 1ms to your request handling time
250+
- **Automatic batching**: Events are queued and sent in batches (default: every 2 seconds or 20 events)
251+
252+
```ruby
253+
# This returns immediately - no HTTP request is made here
254+
client.track('Button Clicked', { button: 'signup' }, user_id: 'user_123')
255+
256+
# Your code continues without waiting
257+
render json: { success: true }
258+
```
259+
260+
The only blocking operation is `flush()`, which waits for all queued events to be sent. This is typically only called during graceful shutdown.
261+
153262
## Configuration
154263

155264
### Default Values
@@ -161,6 +270,28 @@ client.shutdown
161270
- `retry_initial_delay`: 1000ms
162271
- `flush_on_shutdown`: true
163272

273+
### Logging
274+
275+
```ruby
276+
Klime.configure do |config|
277+
config.logger = Rails.logger
278+
end
279+
```
280+
281+
### Callbacks
282+
283+
```ruby
284+
Klime.configure do |config|
285+
config.on_error = Proc.new { |error, batch|
286+
Sentry.capture_exception(error)
287+
}
288+
289+
config.on_success = Proc.new { |response|
290+
Rails.logger.info "Sent #{response.accepted} events"
291+
}
292+
end
293+
```
294+
164295
## Error Handling
165296

166297
The SDK automatically handles:
@@ -169,6 +300,8 @@ The SDK automatically handles:
169300
- **Permanent errors** (400, 401): Logs error and drops event
170301
- **Rate limiting**: Respects `Retry-After` header
171302

303+
For synchronous operations, use bang methods (`track!`, `identify!`, `group!`) which raise `Klime::SendError` on failure.
304+
172305
## Size Limits
173306

174307
- Maximum event size: 200KB
@@ -183,12 +316,12 @@ Events exceeding these limits are rejected and logged.
183316
# config/initializers/klime.rb
184317
require 'klime'
185318

186-
KLIME = Klime::Client.new(
187-
write_key: ENV['KLIME_WRITE_KEY']
188-
)
319+
Klime.configure do |config|
320+
config.logger = Rails.logger
321+
end
189322

190-
# Ensure graceful shutdown
191-
at_exit { KLIME.shutdown }
323+
Klime.client = Klime::Client.new(write_key: ENV['KLIME_WRITE_KEY'])
324+
KLIME = Klime.client
192325
```
193326

194327
```ruby
@@ -197,13 +330,21 @@ class ButtonsController < ApplicationController
197330
def click
198331
KLIME.track('Button Clicked', {
199332
button_name: params[:button_name]
200-
}, user_id: current_user&.id&.to_s, ip: request.remote_ip)
333+
}, user_id: current_user&.id&.to_s)
201334

202335
render json: { success: true }
203336
end
204337
end
205338
```
206339

340+
For Puma with multiple workers, add to `config/puma.rb`:
341+
342+
```ruby
343+
on_worker_boot do
344+
Klime.restart!
345+
end
346+
```
347+
207348
## Sinatra Example
208349

209350
```ruby
@@ -217,7 +358,7 @@ post '/api/button-clicked' do
217358

218359
client.track('Button Clicked', {
219360
button_name: data['buttonName']
220-
}, user_id: data['userId'], ip: request.ip)
361+
}, user_id: data['userId'])
221362

222363
{ success: true }.to_json
223364
end
@@ -226,35 +367,6 @@ end
226367
at_exit { client.shutdown }
227368
```
228369

229-
## Rack Middleware Example
230-
231-
```ruby
232-
# lib/klime_middleware.rb
233-
require 'klime'
234-
235-
class KlimeMiddleware
236-
def initialize(app, write_key:)
237-
@app = app
238-
@client = Klime::Client.new(write_key: write_key)
239-
end
240-
241-
def call(env)
242-
status, headers, response = @app.call(env)
243-
244-
# Track page views for authenticated users
245-
user_id = env['rack.session']&.dig('user_id')
246-
if user_id && env['REQUEST_METHOD'] == 'GET' && status == 200
247-
@client.track('Page View', {
248-
path: env['PATH_INFO'],
249-
method: env['REQUEST_METHOD']
250-
}, user_id: user_id, ip: env['REMOTE_ADDR'])
251-
end
252-
253-
[status, headers, response]
254-
end
255-
end
256-
```
257-
258370
## Requirements
259371

260372
- Ruby 2.6 or higher

klime.gemspec

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Gem::Specification.new do |spec|
44
spec.name = "klime"
5-
spec.version = "1.0.4"
5+
spec.version = "1.1.0"
66
spec.authors = ["Klime"]
77
spec.email = ["[email protected]"]
88

@@ -21,7 +21,8 @@ Gem::Specification.new do |spec|
2121

2222
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
2323

24-
# No runtime dependencies - uses only standard library
24+
# Runtime dependency - logger is being removed from default gems in Ruby 3.5
25+
spec.add_dependency "logger"
2526

2627
# Development dependencies
2728
spec.add_development_dependency "minitest", "~> 5.0"

0 commit comments

Comments
 (0)