> ## 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.

# Rust SDK

> Track events and logs from Rust services with sub-microsecond overhead.

The Rust 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 worker handles batching and delivery over TCP.

Your thread does about 80 ns of work per call (serialize, encode, enqueue) and never touches the network.

## Installation

```bash theme={null}
cargo add tell
cargo add tokio --features rt-multi-thread,macros
```

The SDK requires Rust 2024 edition and a Tokio runtime.

## Quick start

```rust theme={null}
use tell::{Tell, TellConfig, props};

#[tokio::main]
async fn main() {
    let client = Tell::new(
        TellConfig::production("a1b2c3d4e5f60718293a4b5c6d7e8f90").unwrap()
    ).unwrap();

    client.track("user_123", "Page Viewed", props! {
        "url" => "/home",
        "referrer" => "google"
    });

    client.close().await.ok();
}
```

That's a working setup. 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.

```rust theme={null}
// Production — collect.tell.rs:50000, batch 100, flush 10s
let config = TellConfig::production("a1b2c3d4e5f60718293a4b5c6d7e8f90").unwrap();

// Development — localhost:50000, batch 10, flush 2s, debug logging
let config = TellConfig::development("a1b2c3d4e5f60718293a4b5c6d7e8f90").unwrap();
```

For custom settings, use the builder:

```rust theme={null}
use std::time::Duration;

let config = TellConfig::builder("a1b2c3d4e5f60718293a4b5c6d7e8f90")
    .endpoint("collect.internal:50000")
    .batch_size(200)
    .flush_interval(Duration::from_secs(5))
    .max_retries(5)
    .close_timeout(Duration::from_secs(10))
    .network_timeout(Duration::from_secs(15))
    .on_error(|e| eprintln!("[Tell] {e}"))
    .build()
    .unwrap();
```

The API key must be a 32-character hex string. The SDK validates it at config time and returns an error if it's invalid.

## Tracking events

Every tracking method takes `user_id` as its first parameter. Calls never block or panic — errors go to the optional `on_error` callback.

```rust theme={null}
// Track a custom event
client.track("user_123", "Feature Used", props! {
    "feature" => "dark_mode",
    "enabled" => true
});

// Identify a user
client.identify("user_123", props! {
    "name" => "Jane",
    "plan" => "pro",
    "company" => "Acme"
});

// Associate user with a group
client.group("user_123", "company_456", props! {
    "plan" => "enterprise",
    "seats" => 50
});

// Track revenue
client.revenue("user_123", 49.99, "USD", "order_789", props! {
    "product" => "annual_plan"
});

// Link two user identities
client.alias("anon_abc", "user_123");
```

### Standard event names

The SDK provides typed constants for common events so you don't have to remember exact strings:

```rust theme={null}
use tell::Events;

client.track("user_123", Events::PAGE_VIEWED, props! { "url" => "/pricing" });
client.track("user_123", Events::USER_SIGNED_UP, props! { "source" => "organic" });
client.track("user_123", Events::ORDER_COMPLETED, props! { "total" => 99.00 });
client.track("user_123", Events::FEATURE_USED, props! { "feature" => "export" });
```

Constants are available for 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:

```rust theme={null}
client.register(props! { "app_version" => "2.1.0", "env" => "production" });

// This track call automatically includes app_version and env
client.track("user_123", "Click", props! { "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.

```rust theme={null}
client.log_error("DB connection failed", Some("api"), props! {
    "host" => "db.internal",
    "retries" => 3
});

client.log_info("Request processed", Some("api"), props! {
    "status" => 200,
    "duration_ms" => 45
});

client.log_warning("Rate limit approaching", Some("gateway"), None::<serde_json::Value>);
```

The `service` parameter is optional — pass `None` to default to `"app"`. 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:

```rust theme={null}
use tell_encoding::LogLevel;

client.log(LogLevel::Error, "Something broke", Some("worker"), props! {
    "job_id" => "j_123"
});
```

## Properties

You have three ways to pass properties:

```rust theme={null}
use tell::{props, Props};

// 1. props! macro — fastest, zero intermediate allocation
client.track("user_123", "Click", props! {
    "url" => "/home",
    "count" => 42,
    "active" => true
});

// 2. Props builder — for dynamic values
let p = Props::new()
    .add("url", &request.path)
    .add("status", response.status);
client.track("user_123", "Request", p);

// 3. serde_json — works with any Serialize type
client.track("user_123", "Click", Some(json!({"url": "/home"})));

// 4. No properties
client.track("user_123", "Click", None::<serde_json::Value>);
```

`Props` and `props!` write JSON bytes directly to a buffer, skipping the intermediate `serde_json::Value` allocation. Use them on hot paths.

## Lifecycle

```rust theme={null}
// Force-send all queued events and logs
client.flush().await?;

// Rotate session ID
client.reset_session();

// Flush + shut down the background worker
client.close().await?;
```

Always call `close()` before your process exits to avoid losing buffered events. It blocks up to `close_timeout` (default 5 seconds).

## Sharing across threads

`Tell` is `Clone + Send + Sync`. Internally it wraps everything in an `Arc`, so cloning is cheap:

```rust theme={null}
let client = Tell::new(config).unwrap();

let c = client.clone();
tokio::spawn(async move {
    c.track("user_456", "Background Job", props! { "job" => "sync" });
});
```

## Error handling

The constructor and lifecycle methods return `Result`:

```rust theme={null}
let client = Tell::new(config)?;   // TellError::Configuration if invalid
client.flush().await?;              // TellError::Network on failure
client.close().await?;              // TellError::Closed if already shut down
```

Tracking and logging calls (`track`, `identify`, `log_*`, etc.) never return errors. Invalid input (empty user ID, event name too long) is reported through the `on_error` callback:

```rust theme={null}
let config = TellConfig::builder("a1b2c3d4e5f60718293a4b5c6d7e8f90")
    .on_error(|e| eprintln!("[Tell] {e}"))
    .build()
    .unwrap();
```

## 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 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         |

### Retry behavior

On TCP send failure, the SDK retries with exponential backoff: 1s initial delay, 1.5x multiplier, 20% jitter, capped at 30s. After `max_retries` attempts, the batch is dropped and the error reported via `on_error`.
