How to set up analytics in Rust
Feb 27, 2024
Product analytics enable you to gather and analyze data about how users interact with your Rust app. To show you how to set up analytics, in this tutorial we create a basic Rust app, add PostHog, and use it to capture events and create insights.
1. Create a basic Rust app
We start by creating a simple Rust app that has two pages:
- A
login
page where a user can enter their name, email, and company name in a form. - A
dashboard
page that has some text and a button.
First, ensure Rust is installed. Then create a new project:
cargo new rust_analyticscd rust_analytics
Add the following dependencies to your Cargo.toml
:
[package]name = "rust_analytics"version = "0.1.0"edition = "2021"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]actix-web = "4"actix-session = "0.5"serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }
Then, we set up our routes and HTML pages. Replace the code in src/main.rs
with the following:
use actix_session::{CookieSession, Session};use actix_web::{web, App, HttpResponse, HttpServer, Responder, http::header};use serde::Deserialize;async fn login_form() -> impl Responder {HttpResponse::Ok().content_type("text/html").body(r#"<!DOCTYPE html><html><head><title>Login</title><style>label, input, button {display: block;margin-bottom: 10px;}</style></head><body><form action="/auth" method="POST"><label for="name">Name:</label><input type="text" name="name" required><label for="email">Email:</label><input type="email" name="email" required><label for="company_name">Company Name:</label><input type="text" name="company_name" required><button type="submit">Log in</button></form></body></html>"#,)}async fn dashboard(session: Session) -> impl Responder {if let Some(name) = session.get::<String>("name").unwrap() {let company_name = session.get::<String>("company_name").unwrap().unwrap_or_default();HttpResponse::Ok().content_type("text/html").body(format!(r#"<!DOCTYPE html><html><head><title>Dashboard</title><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script><script>$(document).ready(function() {{$('#dashboardForm').on('submit', function(e) {{e.preventDefault();$.ajax({{url: '/api/dashboard',type: 'POST',}});}});}});</script></head><body><h1>Welcome, {} from {}!</h1><form id="dashboardForm" method="POST"><button type="submit">Click Me</button></form></body></html>"#,name, company_name))} else {HttpResponse::Found().append_header((header::LOCATION, "/")).finish()}}#[derive(Deserialize)]struct AuthData {name: String,email: String,company_name: String,}async fn authenticate(form: web::Form<AuthData>, session: Session) -> impl Responder {session.insert("name", &form.name).unwrap();session.insert("company_name", &form.company_name).unwrap();session.insert("email", &form.email).unwrap();HttpResponse::Found().append_header((header::LOCATION, "/dashboard")).finish()}async fn api_dashboard() -> impl Responder {// We'll update this code laterHttpResponse::Ok().body("API endpoint reached")}#[actix_web::main]async fn main() -> std::io::Result<()> {HttpServer::new(|| {App::new().wrap(CookieSession::signed(&[0; 32]).secure(false)).route("/", web::get().to(login_form)).route("/dashboard", web::get().to(dashboard)).route("/auth", web::post().to(authenticate)).route("/api/dashboard", web::post().to(api_dashboard))}).bind("127.0.0.1:8000")?.run().await}
Run cargo run
and navigate to http://localhost:8000
to see our app in action. Enter anything on the login page to save some session details.
2. Add PostHog to your app
With our app set up, it’s time to install and set up PostHog. If you don't have a PostHog instance, you can sign up for free. Make sure you sign for us.posthog.com
and not eu.posthog.com
as currently our Rust SDK only supports the US cloud.
Add posthog-rs
to your Cargo.toml
to install PostHog's Rust SDK:
# rest of config[dependencies]posthog-rs = "0.2.0"# rest of config
With this set up, we're ready to capture events.
3. Implement the event capture code
To show how to capture events with PostHog, we capture an event when the button on the dashboard
page is clicked. To do this, we call posthog.capture()
.
First, import posthog_rs::Event
at the top of main.rs
. We also need to import tokio::task
to convert our asynchronous capture request to synchronous:
// other importsuse posthog_rs::Event;use tokio::task;
Then update our api_dashboard()
function to initialize a PostHog client and capture an event. You'll need your project API key for this, which you can find in your project settings:
async fn api_dashboard(session: Session) -> impl Responder {if let Some(user_email) = session.get::<String>("email").unwrap() {let _result = task::spawn_blocking(move || {let client: posthog_rs::Client = posthog_rs::client("<ph_project_api_key>");let event = Event::new("dashboard_api_called", &user_email);client.capture(event).unwrap();}).await;}HttpResponse::Ok().body("API endpoint reached")}
With this set up, re-run your app and click the button on the dashboard
page a few times. You should now see the captured event in your PostHog activity tab.
💡 PostHog tip: Setting the correct
distinct_id
When calling
posthog.capture()
, you need to provide adistinct_id
argument in your event. This is a unique identifier for your user and enables you to correctly attribute events to them.For logged-in users, you typically use their email or database ID. For logged-out or anonymous users, you should use a unique identifier, either generated by you or the PostHog JavaScript web library (which can then be accessed in the cookies. See an example of accessing the PostHog cookie in our Nuxt tutorial).
Setting event properties
When capturing events, you can optionally include additional information by setting the properties
argument. This is helpful for breaking down or filtering events when creating insights.
As an example, we add the user's name as an event property:
async fn api_dashboard(session: Session) -> impl Responder {if let (Some(user_email), Some(user_name)) = (session.get::<String>("email").unwrap(),session.get::<String>("name").unwrap()) {let _result = task::spawn_blocking(move || {let client: posthog_rs::Client = posthog_rs::client("<ph_project_api_key>");let mut event = Event::new("dashboard_api_called", &user_email);event.insert_prop("user_name", user_name).unwrap();client.capture(event).unwrap();}).await;}HttpResponse::Ok().body("API endpoint reached")}
4. Create an insight in PostHog
Restart your app and capture events using different inputs in the login
page. This will capture events for different users and enable us to show the power of PostHog insights.
Next, go to the Product analytics tab in PostHog and click the + New insight button. PostHog supports many different types of insights, such as trends, funnels, paths and more.
In this tutorial, we create a simple trend insight:
- Select the Trends tab.
- Under the Series header select the
dashboard_api_called
event. - Click the Total count dropdown to change how events are aggregated. You can choose options such as
Count per user
,Unique users
, and more. You can also add filters or breakdown based on properties.
For example, in the image below we set our insight to show the total count of events dashboard_api_called
events where the user_name
property is equal to Max Hedgehog
:
That's it! Feel free to play around in your dashboard and explore the different kinds of insights you can create in PostHog.
Further reading
- Using the PostHog API to capture events
- How to use the PostHog API to get insights and persons
- What to do after installing PostHog in 5 steps
Subscribe to our newsletter
Product for Engineers
Sharing what we learn about building successful products. Read by 25,000+ founders and developers.
We'll share your email with Substack