# ic-analytics-sdk
Rust SDK for sending metrics from any Internet Computer canister to an
[ICAnalytics](https://icanalytics.io) analytics canister.
## Installation
Add the crate to your canister's `Cargo.toml`:
```toml
[dependencies]
ic-analytics-sdk = "0.2.2"
```
Or with `cargo add`:
```bash
cargo add ic-analytics-sdk
```
---
## Quick start
### 1. Initialise the client
`AnalyticsClient` holds the principal of your analytics canister. Create it
once and store it in a `thread_local!`.
```rust
use ic_analytics_sdk::AnalyticsClient;
use candid::Principal;
thread_local! {
static ANALYTICS: AnalyticsClient = AnalyticsClient::new(
Principal::from_text("YOUR-ANALYTICS-CANISTER-ID").unwrap()
);
}
```
Find your analytics canister ID on the ICAnalytics dashboard after creating
an analytics canister for your Dapp.
### 2. Record a metric
All recording is **fire-and-forget** — `record_metric` enqueues a one-way
inter-canister call that does not block your canister's execution.
```rust
use ic_analytics_sdk::{Metric, MetricValue};
key: "user_signups".to_string(),
name: "User Sign-ups".to_string(),
value: MetricValue::Counter(1),
})
.expect("record_metric failed");
});
```
---
## Metric types
| `Counter(i64)` | Delta (positive or negative) | Accumulated running total |
| `Gauge(f64)` | Absolute value | Overwritten on each call |
| `Histogram { x: f64, y: f64 }` | (x, y) data point | Appended with each call; use the canister timestamp as `x` for time-series data |
| `Log(String)` | Text entry | Appended with canister timestamp |
The `key` field is the stable identifier for a metric. The `name` field is the
human-readable label shown in the dashboard. The key is fixed on first write —
changing the metric type for an existing key will trap.
---
## Examples
### Counter
Increment a running total. Pass a negative delta to decrement.
```rust
a.record_metric(Metric {
key: "transfers_total".to_string(),
name: "Total Transfers".to_string(),
value: MetricValue::Counter(1),
})?;
```
### Gauge
Store the latest absolute value. Useful for memory usage, queue depth, etc.
```rust
let heap_mb = (ic_cdk::api::stable_size() * 65536) as f64 / 1_048_576.0;
a.record_metric(Metric {
key: "heap_usage_mb".to_string(),
name: "Heap Usage (MB)".to_string(),
value: MetricValue::Gauge(heap_mb),
})?;
```
### Histogram
Append an (x, y) data point. For time-series use, pass the canister timestamp
(nanoseconds) as `x` and the measured value as `y`. Useful for request latency,
throughput, or any trended data.
```rust
let now = ic_cdk::api::time() as f64; // nanoseconds since epoch
a.record_metric(Metric {
key: "response_latency_ms".to_string(),
name: "Response Latency (ms)".to_string(),
value: MetricValue::Histogram { x: now, y: 42.5 },
})?;
```
### Log
Append a timestamped text entry. Useful for events, errors, or audit trails.
```rust
a.record_metric(Metric {
key: "canister_events".to_string(),
name: "Canister Events".to_string(),
value: MetricValue::Log("[INFO] Upgrade completed to v2.1.0".to_string()),
})?;
```
---
## Full example
```rust
use candid::Principal;
use ic_analytics_sdk::{AnalyticsClient, Metric, MetricValue};
use ic_cdk_macros::{init, update};
use std::cell::RefCell;
thread_local! {
static ANALYTICS: RefCell<Option<AnalyticsClient>> = RefCell::new(None);
}
#[init]
fn init(analytics_canister_id: Principal) {
ANALYTICS.with(|a| {
*a.borrow_mut() = Some(AnalyticsClient::new(analytics_canister_id));
});
}
#[update]
fn transfer(to: Principal, amount: u64) -> Result<(), String> {
// ... your transfer logic ...
ANALYTICS.with(|a| {
if let Some(client) = a.borrow().as_ref() {
let _ = client.record_metric(Metric {
key: "transfers_total".to_string(),
name: "Transfers".to_string(),
value: MetricValue::Counter(1),
});
let _ = client.record_metric(Metric {
key: "transfer_log".to_string(),
name: "Transfer Log".to_string(),
value: MetricValue::Log(format!("Transfer of {} to {}", amount, to)),
});
}
});
Ok(())
}
```
---
## API Reference
### `AnalyticsClient`
```rust
pub struct AnalyticsClient {
pub backend_canister_id: Principal,
}
impl AnalyticsClient {
/// Creates a new client pointing at the given analytics canister.
pub fn new(backend_canister_id: Principal) -> Self;
/// Enqueues a one-way call to record a metric. Non-blocking.
pub fn record_metric(&self, metric: Metric) -> Result<(), String>;
}
```
### `Metric`
```rust
pub struct Metric {
/// Stable identifier used to look up this metric. Fixed on first write.
pub key: String,
/// Human-readable label shown in the dashboard.
pub name: String,
/// The value and its storage semantics.
pub value: MetricValue,
}
```
### `MetricValue`
```rust
pub enum MetricValue {
Counter(i64), // Delta applied to a running total
Gauge(f64), // Absolute value; replaces previous
Histogram { x: f64, y: f64 }, // (x, y) data point; appended with each call
Log(String), // Appended with canister timestamp
}
```