Repository: meilisearch/meilisearch
File: meilisearch-http/src/analytics/segment_analytics.rs (Rust)

Code before:
    update_documents_aggregator: DocumentsAggregator,
}

impl Segment {
    fn compute_traits(opt: &Opt, stats: Stats) -> Value {
        static FIRST_START_TIMESTAMP: Lazy<Instant> = Lazy::new(Instant::now);
        static SYSTEM: Lazy<Value> = Lazy::new(|| {
            let mut sys = System::new_all();
            sys.refresh_all();
            let kernel_version = sys
                .kernel_version()
                .map(|k| k.split_once("-").map(|(k, _)| k.to_string()))
                .flatten();
            json!({
                    "distribution": sys.name(),
                    "kernel_version": kernel_version,
                    "cores": sys.processors().len(),
                    "ram_size": sys.total_memory(),
                    "disk_size": sys.disks().iter().map(|disk| disk.total_space()).max(),
                    "server_provider": std::env::var("MEILI_SERVER_PROVIDER").ok(),
            })
        });
        let infos = {
            let db_path = opt.db_path != PathBuf::from("./data.ms");
            let import_dump = opt.import_dump.is_some();
            let dumps_dir = opt.dumps_dir != PathBuf::from("dumps/");
            let import_snapshot = opt.import_snapshot.is_some();
            let http_addr = opt.http_addr != "127.0.0.1:7700";

            let mut infos = serde_json::to_value(opt).unwrap();
            infos["db_path"] = json!(db_path);
            infos["import_dump"] = json!(import_dump);
            infos["dumps_dir"] = json!(dumps_dir);
            infos["import_snapshot"] = json!(import_snapshot);
            infos["http_addr"] = json!(http_addr);
        };

        let number_of_documents = stats
            .indexes
            .values()
            .map(|index| index.number_of_documents)
            .collect::<Vec<u64>>();

        json!({
            "start_since_days": FIRST_START_TIMESTAMP.elapsed().as_secs() / (60 * 60 * 24), // one day
            "system": *SYSTEM,
            "stats": {
                "database_size": stats.database_size,
                "indexes_number": stats.indexes.len(),
                "documents_number": number_of_documents,
            },

Review comment (bug):
Also, what I don't like here is that we serialize all of the Argument struct and pick the field that we don't want to for privacy reasons instead of doing it the other, safer, way: picking the struct fields that we care about and are safe. Doing it the other way can work with privacy if we forgot about modifying this block of code: we would lose information instead of getting too much from the user i.e. privacy argument.

Or maybe I am wrong and the `#[serde(skip)]` thing is enough.

Code after:
    add_documents_aggregator: DocumentsAggregator,
    update_documents_aggregator: DocumentsAggregator,
}

impl Segment {
    fn compute_traits(opt: &Opt, stats: Stats) -> Value {
        static FIRST_START_TIMESTAMP: Lazy<Instant> = Lazy::new(Instant::now);
        static SYSTEM: Lazy<Value> = Lazy::new(|| {
            let mut sys = System::new_all();
            sys.refresh_all();
            let kernel_version = sys
                .kernel_version()
                .map(|k| k.split_once("-").map(|(k, _)| k.to_string()))
                .flatten();
            json!({
                    "distribution": sys.name(),
                    "kernel_version": kernel_version,
                    "cores": sys.processors().len(),
                    "ram_size": sys.total_memory(),
                    "disk_size": sys.disks().iter().map(|disk| disk.total_space()).max(),
                    "server_provider": std::env::var("MEILI_SERVER_PROVIDER").ok(),
            })
        });
        // The infos are all cli option except every option containing sensitive information.
        // We consider an information as sensible if it contains a path, an address or a key.
        let infos = {
            // First we see if any sensitive fields were used.
            let db_path = opt.db_path != PathBuf::from("./data.ms");
            let import_dump = opt.import_dump.is_some();
            let dumps_dir = opt.dumps_dir != PathBuf::from("dumps/");
            let import_snapshot = opt.import_snapshot.is_some();
            let snapshots_dir = opt.snapshot_dir != PathBuf::from("snapshots/");
            let http_addr = opt.http_addr != "127.0.0.1:7700";

            let mut infos = serde_json::to_value(opt).unwrap();

            // Then we overwrite all sensitive field with a boolean representing if
            // the feature was used or not.
            infos["db_path"] = json!(db_path);
            infos["import_dump"] = json!(import_dump);
            infos["dumps_dir"] = json!(dumps_dir);
            infos["import_snapshot"] = json!(import_snapshot);
            infos["snapshot_dir"] = json!(snapshots_dir);
            infos["http_addr"] = json!(http_addr);

            infos
        };

        let number_of_documents = stats
            .indexes
            .values()