Documentation Index
Fetch the complete documentation index at: https://docs.tell.rs/llms.txt
Use this file to discover all available pages before exploring further.
The C++ SDK sends events and structured logs from your services to Tell. It’s a server SDK — you create a client, pass a user ID on every call, and a background worker thread handles batching and delivery over TCP.
Your thread does about 85 ns of work per call and never touches the network. The SDK has zero external dependencies beyond C++17 and pthreads.
Installation
Add the SDK as a CMake subdirectory:
add_subdirectory(tell-cpp)
target_link_libraries(your_target PRIVATE tell)
Or build from source:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
Requires a C++17 compiler (GCC 7+, Clang 5+, MSVC 2017+) and CMake 3.14+.
Quick start
#include "tell/tell.hpp"
int main() {
auto client = tell::Tell::create(
tell::TellConfig::production("a1b2c3d4e5f60718293a4b5c6d7e8f90")
);
client->track("user_123", "Page Viewed",
tell::Props().add("url", "/home").add("referrer", "google"));
client->close();
}
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 build a custom config.
// Production — collect.tell.rs:50000, batch 100, flush 10s
auto config = tell::TellConfig::production("a1b2c3d4e5f60718293a4b5c6d7e8f90");
// Development — localhost:50000, batch 10, flush 2s
auto config = tell::TellConfig::development("a1b2c3d4e5f60718293a4b5c6d7e8f90");
For custom settings, use the builder:
auto config = tell::TellConfig::builder("a1b2c3d4e5f60718293a4b5c6d7e8f90")
.endpoint("collect.internal:50000")
.batch_size(200)
.flush_interval(std::chrono::milliseconds(5000))
.max_retries(5)
.close_timeout(std::chrono::milliseconds(10000))
.network_timeout(std::chrono::milliseconds(15000))
.on_error([](const tell::TellError& e) {
std::cerr << "[Tell] " << e.what() << std::endl;
})
.build();
The API key must be a 32-character hex string. The SDK validates it at build time and throws TellError if it’s invalid.
Tracking events
Every tracking method takes user_id as its first parameter. Calls never block or throw — errors go to the optional on_error callback.
// Track a custom event
client->track("user_123", "Feature Used",
tell::Props().add("feature", "dark_mode").add("enabled", true));
// Identify a user
client->identify("user_123",
tell::Props().add("name", "Jane").add("plan", "pro"));
// Associate user with a group
client->group("user_123", "company_456",
tell::Props().add("plan", "enterprise").add("seats", 50));
// Track revenue
client->revenue("user_123", 49.99, "USD", "order_789",
tell::Props().add("product", "annual_plan"));
// Link two user identities
client->alias("anon_abc", "user_123");
Properties are always optional — you can omit them entirely:
client->track("user_123", "Click");
Standard event names
The SDK provides typed constants for common events:
client->track("user_123", tell::Events::PAGE_VIEWED,
tell::Props().add("url", "/pricing"));
client->track("user_123", tell::Events::USER_SIGNED_UP,
tell::Props().add("source", "organic"));
client->track("user_123", tell::Events::ORDER_COMPLETED,
tell::Props().add("total", 99.00));
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_props(
tell::Props().add("app_version", "2.1.0").add("env", "production"));
// This track call automatically includes app_version and env
client->track("user_123", "Click",
tell::Props().add("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->log_error("DB connection failed", "api",
tell::Props().add("host", "db.internal").add("retries", 3));
client->log_info("Request processed", "api",
tell::Props().add("status", 200).add("duration_ms", 45));
client->log_warning("Rate limit approaching", "gateway");
The service parameter defaults to "app" if omitted. Convenience methods are available for all nine levels: log_emergency, log_alert, log_critical, log_error, log_warning, log_notice, log_info, log_debug, and log_trace.
You can also use the generic log method with an explicit level:
client->log(tell::LogLevel::Notice, "Deployment completed", "deploy",
tell::Props().add("version", "3.1.0").add("commit", "abc123f"));
Properties
The Props class builds key-value pairs with a chainable API. It writes JSON bytes directly to a buffer — no intermediate allocations.
tell::Props()
.add("url", "/home") // string
.add("count", 42) // int
.add("active", true) // bool
.add("score", 3.14); // double
Supported types: std::string, const char*, int, int64_t, double, and bool.
Lifecycle
// Force-send all queued events and logs
client->flush();
// Rotate session ID
client->reset_session();
// Flush + shut down the background worker
client->close();
Always call close() before your process exits to avoid losing buffered events. It blocks up to close_timeout (default 5 seconds).
Thread safety
The SDK is fully thread-safe. Internal state is protected by std::shared_mutex:
auto client = tell::Tell::create(config);
std::thread t1([&] { client->track("user_1", "Event A"); });
std::thread t2([&] { client->track("user_2", "Event B"); });
std::thread t3([&] { client->log_info("Processing", "worker"); });
t1.join(); t2.join(); t3.join();
client->close();
Error handling
Construction throws on invalid config:
try {
auto client = tell::Tell::create(config);
} catch (const tell::TellError& e) {
std::cerr << e.what() << std::endl;
}
Tracking and logging calls (track, identify, log_*, etc.) never throw. Invalid input (empty user ID, event name too long) is reported through the on_error callback:
auto config = tell::TellConfig::builder("a1b2c3d4e5f60718293a4b5c6d7e8f90")
.on_error([](const tell::TellError& e) {
if (e.kind() == tell::ErrorKind::Validation) {
std::cerr << "Invalid " << e.field()
<< ": " << e.reason() << std::endl;
}
})
.build();
Advanced
Configuration reference
| Parameter | Default (production) | Default (development) | Description |
|---|
endpoint | collect.tell.rs:50000 | localhost:50000 | TCP collector address |
batch_size | 100 | 10 | Events per batch before auto-flush |
flush_interval | 10s | 2s | Time between auto-flushes |
max_retries | 3 | 3 | Retry attempts on send failure |
close_timeout | 5s | 5s | Max wait on close() |
network_timeout | 30s | 30s | TCP connect/send timeout |
on_error | silent | silent | Error callback |
Validation rules
| Field | Rule |
|---|
| API key | 32-character hex string |
| User ID | Non-empty |
| Event name | 1–256 characters |
| Log message | 1–65,536 characters |
| Group ID | Non-empty |
| Revenue amount | Positive number |
| Service | Max 256 characters |
Caller latency — serialize, encode, and enqueue (Apple M4 Pro):
| Operation | Latency |
|---|
track (no props) | 85 ns |
track (with props) | 224 ns |
identify | 272 ns |
log | 273 ns |
revenue | 326 ns |
Retry behavior
On TCP send failure, the SDK retries with exponential backoff up to max_retries attempts. After exhausting retries, the batch is dropped and the error reported via on_error.