Skip to main content

hinge_rs/
logging.rs

1/// Library crates should not install a process-global logger.
2///
3/// Applications can install `tracing_subscriber`, `env_logger`, or any other
4/// logger before constructing the client.
5pub fn init_logger() {}
6
7fn sensitive_header_value(
8    name: &reqwest::header::HeaderName,
9    value: &reqwest::header::HeaderValue,
10) -> String {
11    let name = name.as_str();
12    if name.eq_ignore_ascii_case("authorization") {
13        return "Bearer ***REDACTED***".to_string();
14    }
15    if name.eq_ignore_ascii_case("sb-access-token")
16        || name.eq_ignore_ascii_case("session-key")
17        || name.eq_ignore_ascii_case("x-session-key")
18    {
19        return "***REDACTED***".to_string();
20    }
21    if name.eq_ignore_ascii_case("x-session-id")
22        || name.eq_ignore_ascii_case("x-device-id")
23        || name.eq_ignore_ascii_case("x-install-id")
24    {
25        return format!(
26            "***{}",
27            &value
28                .to_str()
29                .unwrap_or("")
30                .chars()
31                .rev()
32                .take(4)
33                .collect::<String>()
34                .chars()
35                .rev()
36                .collect::<String>()
37        );
38    }
39    value.to_str().unwrap_or("").to_string()
40}
41
42/// Format HTTP headers for logging (hiding sensitive data)
43pub fn format_headers(headers: &reqwest::header::HeaderMap) -> String {
44    let mut output = String::new();
45    for (name, value) in headers.iter() {
46        let value_str = sensitive_header_value(name, value);
47        output.push_str(&format!("  {}: {}\n", name.as_str(), value_str));
48    }
49    output
50}
51
52/// Format WebSocket headers for logging (hiding sensitive data)
53pub fn format_ws_headers(headers: &[(impl AsRef<str>, impl AsRef<str>)]) -> String {
54    let mut output = String::new();
55    for (name, value) in headers.iter() {
56        let n = name.as_ref();
57        let v = value.as_ref();
58        let value_str = if n.eq_ignore_ascii_case("SENDBIRD-WS-AUTH")
59            || n.eq_ignore_ascii_case("SENDBIRD-WS-TOKEN")
60            || n.eq_ignore_ascii_case("sb-access-token")
61            || n.eq_ignore_ascii_case("session-key")
62            || n.eq_ignore_ascii_case("x-session-key")
63        {
64            "***REDACTED***".to_string()
65        } else if n.eq_ignore_ascii_case("Cookie") {
66            "***".to_string()
67        } else if n.eq_ignore_ascii_case("x-session-id")
68            || n.eq_ignore_ascii_case("x-device-id")
69            || n.eq_ignore_ascii_case("x-install-id")
70        {
71            format!(
72                "***{}",
73                v.chars()
74                    .rev()
75                    .take(4)
76                    .collect::<String>()
77                    .chars()
78                    .rev()
79                    .collect::<String>()
80            )
81        } else {
82            v.to_string()
83        };
84        output.push_str(&format!("  {}: {}\n", n, value_str));
85    }
86    output
87}
88
89/// Format JSON for pretty logging
90pub fn format_json(json: &serde_json::Value) -> String {
91    serde_json::to_string_pretty(json).unwrap_or_else(|_| "Invalid JSON".to_string())
92}
93
94/// Log HTTP request details
95pub fn log_request(
96    method: &str,
97    url: &str,
98    headers: &reqwest::header::HeaderMap,
99    body: Option<&serde_json::Value>,
100) {
101    log::info!("━━━━━━━━━━ HTTP REQUEST ━━━━━━━━━━");
102    log::info!("{} {}", method, url);
103    log::debug!("Headers:");
104    log::debug!("{}", format_headers(headers));
105
106    if let Some(body) = body {
107        log::debug!("Body:");
108        log::debug!("{}", format_json(body));
109    }
110    log::info!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
111}
112
113/// Log HTTP response details
114pub fn log_response(
115    status: reqwest::StatusCode,
116    headers: &reqwest::header::HeaderMap,
117    body: Option<&serde_json::Value>,
118) {
119    log::info!("━━━━━━━━━━ HTTP RESPONSE ━━━━━━━━━━");
120    log::info!("Status: {}", status);
121    log::debug!("Headers:");
122    // Log each header on its own line so the logger prefix is consistent
123    for (name, value) in headers.iter() {
124        let value_str = sensitive_header_value(name, value);
125        log::debug!("  {}: {}", name.as_str(), value_str);
126    }
127
128    if let Some(body) = body {
129        log::debug!("Body:");
130        let formatted = format_json(body);
131        // Print full body (no truncation) for clarity during debugging
132        log::debug!("{}", formatted);
133    }
134    log::info!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
135}