openauth-telemetry 0.0.2

Telemetry support for OpenAuth.
Documentation
use std::io::IsTerminal;

use serde_json::json;

fn env_any(keys: &[&str]) -> bool {
    keys.iter().any(|k| std::env::var_os(k).is_some())
}

fn deployment_vendor() -> Option<&'static str> {
    if env_any(&["CF_PAGES", "CF_PAGES_URL", "CF_ACCOUNT_ID"]) {
        return Some("cloudflare");
    }
    if env_any(&["VERCEL", "VERCEL_URL", "VERCEL_ENV"]) {
        return Some("vercel");
    }
    if env_any(&["NETLIFY", "NETLIFY_URL"]) {
        return Some("netlify");
    }
    if env_any(&[
        "RENDER",
        "RENDER_URL",
        "RENDER_INTERNAL_HOSTNAME",
        "RENDER_SERVICE_ID",
    ]) {
        return Some("render");
    }
    if env_any(&[
        "AWS_LAMBDA_FUNCTION_NAME",
        "AWS_EXECUTION_ENV",
        "LAMBDA_TASK_ROOT",
    ]) {
        return Some("aws");
    }
    if env_any(&[
        "GOOGLE_CLOUD_FUNCTION_NAME",
        "GOOGLE_CLOUD_PROJECT",
        "GCP_PROJECT",
        "K_SERVICE",
    ]) {
        return Some("gcp");
    }
    if env_any(&[
        "AZURE_FUNCTION_NAME",
        "FUNCTIONS_WORKER_RUNTIME",
        "WEBSITE_INSTANCE_ID",
        "WEBSITE_SITE_NAME",
    ]) {
        return Some("azure");
    }
    if env_any(&["DENO_DEPLOYMENT_ID", "DENO_REGION"]) {
        return Some("deno-deploy");
    }
    if env_any(&["FLY_APP_NAME", "FLY_REGION", "FLY_ALLOC_ID"]) {
        return Some("fly-io");
    }
    if env_any(&["RAILWAY_STATIC_URL", "RAILWAY_ENVIRONMENT_NAME"]) {
        return Some("railway");
    }
    if env_any(&["DYNO", "HEROKU_APP_NAME"]) {
        return Some("heroku");
    }
    if env_any(&["DO_DEPLOYMENT_ID", "DO_APP_NAME", "DIGITALOCEAN"]) {
        return Some("digitalocean");
    }
    if env_any(&["KOYEB", "KOYEB_DEPLOYMENT_ID", "KOYEB_APP_NAME"]) {
        return Some("koyeb");
    }
    None
}

pub fn detect_system_info() -> serde_json::Value {
    json!({
        "deploymentVendor": deployment_vendor(),
        "systemPlatform": std::env::consts::OS,
        "systemRelease": system_release(),
        "systemArchitecture": std::env::consts::ARCH,
        "cpuCount": std::thread::available_parallelism().ok().map(|count| count.get()),
        "cpuModel": serde_json::Value::Null,
        "cpuSpeed": serde_json::Value::Null,
        "memory": serde_json::Value::Null,
        "isWSL": is_wsl(),
        "isDocker": is_docker(),
        "isTTY": std::io::stdout().is_terminal(),
        "isCI": crate::env::is_ci(),
    })
}

fn system_release() -> serde_json::Value {
    std::fs::read_to_string("/proc/sys/kernel/osrelease")
        .ok()
        .map(|release| serde_json::Value::String(release.trim().to_owned()))
        .unwrap_or(serde_json::Value::Null)
}

fn is_docker() -> bool {
    std::path::Path::new("/.dockerenv").exists()
        || std::path::Path::new("/run/.containerenv").exists()
        || std::fs::read_to_string("/proc/self/cgroup")
            .ok()
            .is_some_and(|content| content.contains("docker"))
}

fn is_wsl() -> bool {
    if std::env::consts::OS != "linux" || is_docker() {
        return false;
    }
    let release = std::fs::read_to_string("/proc/sys/kernel/osrelease").unwrap_or_default();
    let version = std::fs::read_to_string("/proc/version").unwrap_or_default();
    release.to_lowercase().contains("microsoft") || version.to_lowercase().contains("microsoft")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn system_info_reports_available_rust_host_fields() {
        let info = detect_system_info();

        assert_eq!(info["systemPlatform"], std::env::consts::OS);
        assert_eq!(info["systemArchitecture"], std::env::consts::ARCH);
        assert!(info["cpuCount"].as_u64().is_some());
        assert!(info["isTTY"].as_bool().is_some());
    }
}