/ fedimint-core / src / fmt_utils.rs
fmt_utils.rs
  1  use std::{cmp, fmt, ops, thread_local};
  2  
  3  use serde_json::Value;
  4  
  5  pub fn rust_log_full_enabled() -> bool {
  6      // this will be called only once per-thread for best performance
  7      thread_local!(static RUST_LOG_FULL: bool = {
  8          std::env::var_os("RUST_LOG_FULL")
  9              .map(|val| !val.is_empty())
 10              .unwrap_or(false)
 11      });
 12      RUST_LOG_FULL.with(|x| *x)
 13  }
 14  
 15  /// Optional stacktrace formatting for errors.
 16  ///
 17  /// Automatically use `Display` (no stacktrace) or `Debug` depending on
 18  /// `RUST_LOG_FULL` env variable.
 19  ///
 20  /// Meant for logging errors.
 21  pub struct OptStacktrace<T>(pub T);
 22  
 23  impl<T> fmt::Display for OptStacktrace<T>
 24  where
 25      T: fmt::Debug + fmt::Display,
 26  {
 27      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 28          if rust_log_full_enabled() {
 29              fmt::Debug::fmt(&self.0, f)
 30          } else {
 31              fmt::Display::fmt(&self.0, f)
 32          }
 33      }
 34  }
 35  
 36  /// Use for displaying bytes in the logs
 37  ///
 38  /// Will truncate values longer than 64 bytes, unless `RUST_LOG_FULL`
 39  /// environment variable is set to a non-empty value.
 40  pub struct AbbreviateHexBytes<'a>(pub &'a [u8]);
 41  
 42  impl<'a> fmt::Display for AbbreviateHexBytes<'a> {
 43      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 44          if self.0.len() <= 64 || rust_log_full_enabled() {
 45              bitcoin29::hashes::hex::format_hex(self.0, f)?;
 46          } else {
 47              bitcoin29::hashes::hex::format_hex(&self.0[..64], f)?;
 48              f.write_fmt(format_args!("-{}", self.0.len()))?;
 49          }
 50          Ok(())
 51      }
 52  }
 53  
 54  impl<'a> fmt::Debug for AbbreviateHexBytes<'a> {
 55      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 56          fmt::Display::fmt(self, f)
 57      }
 58  }
 59  
 60  /// Use for displaying potentially large `[serde_json::Value]`s in the logs
 61  ///
 62  /// Notably, unlike normal `fmt::Debug` for `serde_json::Value` it does not
 63  /// respect pretty-printing and other formatting settings on the `formatter`.
 64  /// Which for debugging & logs should be OK.
 65  pub struct AbbreviateJson<'a>(pub &'a serde_json::Value);
 66  
 67  // TODO: use `str::floor_char_boundary` instead (when it becomes stable)
 68  // https://github.com/rust-lang/rust/blob/97872b792c9dd6a9bc5c3f4e62a0bd5958b09cdc/library/core/src/str/mod.rs#L258
 69  pub fn floor_char_boundary(s: &str, index: usize) -> usize {
 70      // https://github.com/rust-lang/rust/blob/97872b792c9dd6a9bc5c3f4e62a0bd5958b09cdc/library/core/src/num/mod.rs#L883
 71      #[inline]
 72      pub const fn is_utf8_char_boundary(byte: u8) -> bool {
 73          // This is bit magic equivalent to: b < 128 || b >= 192
 74          (byte as i8) >= -0x40
 75      }
 76  
 77      if index >= s.len() {
 78          s.len()
 79      } else {
 80          let lower_bound = index.saturating_sub(3);
 81          let new_index = s.as_bytes()[lower_bound..=index]
 82              .iter()
 83              .rposition(|b| is_utf8_char_boundary(*b));
 84  
 85          // SAFETY: we know that the character boundary will be within four bytes
 86          unsafe { lower_bound + new_index.unwrap_unchecked() }
 87      }
 88  }
 89  
 90  /// Format json string value if it's too long
 91  fn fmt_abbreviated_str(value: &str, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
 92      const STRING_ABBR_LEN: usize = 128;
 93      fmt::Debug::fmt(
 94          &value[..floor_char_boundary(value, cmp::min(STRING_ABBR_LEN, value.len()))],
 95          formatter,
 96      )?;
 97      if STRING_ABBR_LEN < value.len() {
 98          formatter.write_fmt(format_args!("... {} total", value.len()))?;
 99      }
100      Ok(())
101  }
102  
103  /// Format json array value truncating elements if there's too many, and values
104  /// if they are too long
105  fn fmt_abbreviated_vec(vec: &[Value], formatter: &mut fmt::Formatter) -> fmt::Result {
106      const ARRAY_ABBR_LEN: usize = 64;
107      formatter.write_str("[")?;
108      for (i, v) in vec.iter().enumerate().take(ARRAY_ABBR_LEN) {
109          fmt::Debug::fmt(&AbbreviateJson(v), formatter)?;
110          if i != vec.len() - 1 {
111              formatter.write_str(", ")?;
112          }
113      }
114      if ARRAY_ABBR_LEN < vec.len() {
115          formatter.write_fmt(format_args!("... {} total", vec.len()))?;
116      }
117      formatter.write_str("]")?;
118      Ok(())
119  }
120  
121  /// Format json object value truncating keys if there's too many, and keys and
122  /// values if they are too long
123  fn fmt_abbreviated_object(
124      map: &serde_json::Map<String, Value>,
125      formatter: &mut fmt::Formatter,
126  ) -> fmt::Result {
127      const MAP_ABBR_LEN: usize = 64;
128      formatter.write_str("{")?;
129      for (i, (k, v)) in map.iter().enumerate().take(MAP_ABBR_LEN) {
130          fmt_abbreviated_str(k, formatter)?;
131          formatter.write_str(": ")?;
132          fmt::Debug::fmt(&AbbreviateJson(v), formatter)?;
133          if i != map.len() - 1 {
134              formatter.write_str(", ")?;
135          }
136      }
137      if MAP_ABBR_LEN < map.len() {
138          formatter.write_fmt(format_args!("... {} total", map.len()))?;
139      }
140      formatter.write_str("}")?;
141      Ok(())
142  }
143  
144  impl<'a> fmt::Debug for AbbreviateJson<'a> {
145      fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
146          if rust_log_full_enabled() {
147              std::fmt::Debug::fmt(&self.0, formatter)
148          } else {
149              // modified https://github.com/serde-rs/json/blob/e41ee42d92022dbffc00f4ed50580fa5e060a379/src/value/mod.rs#L177
150              match self.0 {
151                  Value::Null => formatter.write_str("Null"),
152                  Value::Bool(boolean) => write!(formatter, "Bool({boolean})"),
153                  Value::Number(number) => fmt::Debug::fmt(number, formatter),
154                  Value::String(string) => {
155                      formatter.write_str("String(")?;
156                      fmt_abbreviated_str(string, formatter)?;
157                      formatter.write_str(")")
158                  }
159                  Value::Array(vec) => {
160                      formatter.write_str("Array ")?;
161                      fmt_abbreviated_vec(vec, formatter)
162                  }
163                  Value::Object(map) => {
164                      formatter.write_str("Object ")?;
165                      fmt_abbreviated_object(map, formatter)
166                  }
167              }
168          }
169      }
170  }
171  
172  /// Something that can be debug-formatted in an abbreviated way
173  pub trait AbbreviatedDebug {
174      fn abbreviated_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
175  }
176  
177  /// A wrapper that causes the inner `T` to be debug-formatted using
178  /// [`AbbreviatedDebug`]
179  ///
180  /// Useful in situations where using more specific wrapper is not feasible,
181  /// e.g. the value to be abbreviated is nested inside larger struct
182  /// where everything should be `debug-printed` together.
183  pub struct AbbreviateDebug<T>(pub T);
184  
185  impl<T> fmt::Debug for AbbreviateDebug<T>
186  where
187      T: AbbreviatedDebug,
188  {
189      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190          self.0.abbreviated_fmt(f)
191      }
192  }
193  
194  impl<T> ops::Deref for AbbreviateDebug<T> {
195      type Target = T;
196  
197      fn deref(&self) -> &Self::Target {
198          &self.0
199      }
200  }
201  
202  impl AbbreviatedDebug for serde_json::Value {
203      fn abbreviated_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204          fmt::Debug::fmt(&AbbreviateJson(self), f)
205      }
206  }
207  
208  impl<const N: usize> AbbreviatedDebug for [u8; N] {
209      fn abbreviated_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210          fmt::Debug::fmt(&AbbreviateHexBytes(self), f)
211      }
212  }
213  
214  impl AbbreviatedDebug for &[serde_json::Value] {
215      fn abbreviated_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216          fmt_abbreviated_vec(self, f)
217      }
218  }
219  
220  #[cfg(test)]
221  mod tests {
222      use super::*;
223  
224      #[test]
225      fn sanity_check_abbreviate_json() {
226          for v in [
227              serde_json::json!(null),
228              serde_json::json!(true),
229              serde_json::json!(false),
230              serde_json::json!("foo"),
231              serde_json::json!({}),
232              serde_json::json!([]),
233              serde_json::json!([1]),
234              serde_json::json!([1, 3, 4]),
235              serde_json::json!({"a": "b"}),
236              serde_json::json!({"a": "b", "c": "d"}),
237              serde_json::json!({"a": { "foo": "bar"}, "c": "d"}),
238              serde_json::json!({"a": [1, 2, 3, 4], "b": {"c": "d"}}),
239              serde_json::json!([{"a": "b"}]),
240              serde_json::json!([{"a": "b"}, {"d": "f"}]),
241              serde_json::json!([null]),
242          ] {
243              assert_eq!(format!("{:?}", &v), format!("{:?}", AbbreviateJson(&v)));
244          }
245      }
246  }