# Arnis Telemetry Service: How It Sends Generation Logs and Events

> Explore the Arnis telemetry service for sending generation logs. Learn how this opt-in subsystem prioritizes privacy and asynchronous reporting with user consent.

- Repository: [Louis Erbkamm/arnis](https://github.com/louis-e/arnis)
- Tags: internals
- Published: 2026-03-20

---

**The telemetry service in arnis is an opt-in, privacy-focused subsystem that asynchronously reports generation clicks, structured log entries, and crash diagnostics to a remote endpoint while strictly enforcing user consent and isolating all network activity in background threads to prevent UI blocking.**

The arnis repository by louis-e implements a lightweight telemetry module designed to gather diagnostic insights without compromising user privacy or application stability. This service handles three distinct event types—generation clicks, log entries, and crash reports—transmitting them to `https://arnismc.com/telemetry/report_telemetry.php` only when explicit user consent is granted via an atomic boolean flag.

## Architecture and Event Types

The telemetry implementation resides in [`src/telemetry.rs`](https://github.com/louis-e/arnis/blob/main/src/telemetry.rs) and operates as a non-intrusive bridge between the application and analytics infrastructure. All transmissions share common architectural principles: strict consent validation, platform detection, compile-time version injection, and thread-isolated execution.

### Telemetry Event Categories

The service categorizes data into three JSON payloads:

- **Generation clicks**: Simple markers indicating user interaction with the world generation feature, sent via `send_generation_click` (lines 90‑123)
- **Log entries**: Structured diagnostic messages with severity levels, truncated to 1000 characters, transmitted through `send_log` (lines 45‑92)
- **Crash reports**: Panic information captured via custom hooks, truncated to 500 characters, handled by `send_crash_report` (lines 66‑88)

## Core Implementation Details

### Consent Management

At the heart of the privacy controls sits a static atomic boolean initialized to `false`:

```rust
static TELEMETRY_CONSENT: AtomicBool = AtomicBool::new(false);

```

The public API `set_telemetry_consent` (lines 14‑16) allows the GUI to toggle this flag based on user preferences, while `get_telemetry_consent` (lines 18‑21) provides internal access for all telemetry paths. No data transmits unless this function returns `true`.

### Platform Detection and Versioning

Two helper functions enrich every payload with environmental context. The `get_platform` function (lines 23‑31) maps `std::env::consts::OS` to standardized strings (`windows`, `linux`, `macos`), while `get_app_version` (lines 33‑36) injects the crate version at compile time via `env!("CARGO_PKG_VERSION")`.

### JSON Payload Definitions

Serde-annotated structs in [`src/telemetry.rs`](https://github.com/louis-e/arnis/blob/main/src/telemetry.rs) define the wire format. The `LogEntry` struct (lines 54‑63) includes optional platform and version fields:

```rust
#[derive(Serialize)]
struct LogEntry<'a> {
    r#type: &'a str,
    log_level: &'a str,
    log_message: &'a str,
    #[serde(skip_serializing_if = "Option::is_none")]
    platform: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    app_version: Option<&'a str>,
}

```

The `CrashReport` struct (lines 38‑45) and `GenerationClick` struct (lines 47‑52) provide specialized schemas for their respective event types.

## Transmitting Telemetry Events

### Tracking Generation Clicks

The `send_generation_click` function executes when users initiate world generation in the GUI. It validates consent and release build status, then spawns a background thread wrapped in `catch_unwind`:

```rust
pub fn send_generation_click() {
    if !get_telemetry_consent() { return; }
    if cfg!(debug_assertions) { return; }   // Disable in debug builds
    
    std::thread::spawn(|| {
        let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
            let client = Client::new();
            let payload = GenerationClick { r#type: "generation_click" };
            let _ = client.post(TELEMETRY_URL)
                         .header("Content-Type", "application/json")
                         .json(&payload)
                         .send();
        }));
    });
}

```

### Sending Log Entries

The `send_log` function constructs a `LogEntry` containing the severity level, truncated message (limited to 1000 characters), platform, and version. It dispatches via the same background thread pattern, using `std::thread::spawn` and `AssertUnwindSafe` to guarantee that network failures cannot panic the main application.

### Crash Reporting via Panic Hooks

The `install_panic_hook` function (lines 94‑149) registers a custom handler that captures thread panics. After logging locally via the `error!` macro, it extracts error details and invokes `send_crash_report` only when telemetry consent is granted and running in release mode. The implementation uses `catch_unwind` to ensure that the crash reporting itself cannot trigger secondary panics.

## GUI Integration Points

The telemetry system connects to the user interface in [`src/gui.rs`](https://github.com/louis-e/arnis/blob/main/src/gui.rs) at three critical junctions:

- **Startup**: `install_panic_hook()` initializes at line 76 to capture unexpected crashes throughout the application lifecycle
- **Settings**: Line 716 calls `set_telemetry_consent` to persist user preferences from the settings checkbox
- **Generation trigger**: Line 719 invokes `send_generation_click()` immediately after the user requests world generation

Additional modules like `src/world_editor/` and [`src/progress.rs`](https://github.com/louis-e/arnis/blob/main/src/progress.rs) emit log telemetry throughout the processing pipeline via `send_log` calls.

## Summary

- The telemetry service requires explicit opt-in via an `AtomicBool` consent flag that defaults to disabled in [`src/telemetry.rs`](https://github.com/louis-e/arnis/blob/main/src/telemetry.rs) (lines 10‑12)
- Three event types—generation clicks, logs, and crashes—transmit to `https://arnismc.com/telemetry/report_telemetry.php` only in release builds
- All network operations execute in isolated background threads with `std::panic::catch_unwind` protection to prevent UI freezing
- Payloads include platform information from `std::env::consts::OS` and compile-time version strings for environmental context
- Debug builds automatically disable transmission via `cfg!(debug_assertions)` checks to prevent analytics pollution during development

## Frequently Asked Questions

### How does arnis ensure telemetry respects user privacy?

The implementation uses a global `AtomicBool` initialized to `false` (lines 10‑12) with no public setter except `set_telemetry_consent` (lines 14‑16), which the GUI calls only when users explicitly enable telemetry in settings. Every transmission function checks `get_telemetry_consent()` (lines 18‑21) before executing any network code, ensuring silent operation when consent is withheld.

### Why do debug builds not send telemetry?

The code explicitly checks `cfg!(debug_assertions)` at the entry points of `send_generation_click`, `send_log`, and the panic hook. This compile-time configuration prevents debug builds from transmitting data, ensuring that development testing does not pollute production analytics or expose unstable features.

### What prevents telemetry from crashing the application if the network fails?

All transmission logic runs inside `std::thread::spawn` with `std::panic::catch_unwind` wrappers using `AssertUnwindSafe`. This design isolates network failures and potential panics within background threads, guaranteeing that telemetry code cannot block the main UI thread or propagate errors back to the user workflow.

### How are long error messages handled in crash reports?

The panic hook implementation (lines 94‑149) truncates error messages to 500 characters before JSON serialization, while standard log entries truncate at 1000 characters in `send_log`. This prevents oversized payloads while preserving essential diagnostic information for debugging.