Truncate log strings on UTF-8 char boundaries
Slicing a str at a byte index panics when the index falls inside a multi-byte character, so truncate_for_log now walks the limit back to the nearest char boundary before slicing. Co-Authored-By: siGit Code v1.5.12-acp <noreply@sigit.si>
Seto Elkahfi committed
Oct 1, 2026 at 19:58 UTC
2299d31e8982f9a7045c196355e957b9ec9485f6
1 file changed
+32
-1
src/inference/engine.rs
+32
-1
@@ -2096,7 +2096,11 @@ fn chat_role_to_mistral(role: &ChatRole) -> TextMessageRole {
2096
/// Truncate a string for log output, appending `"..."` if truncated.
2097
fn truncate_for_log(s: &str, max_len: usize) -> String {
2098
if s.len() > max_len {
2099
- format!("{}...", &s[..max_len])
2099
+ let mut end = max_len;
2100
+ while !s.is_char_boundary(end) {
2101
+ end -= 1;
2102
+ }
2103
+ format!("{}...", &s[..end])
2104
} else {
2105
s.to_string()
2106
}
@@ -2692,6 +2696,33 @@ mod tests {
2696
assert_eq!(result.len(), 53); // 50 chars + "..."
2697
}
2698
2699
+ #[test]
2700
+ fn truncate_for_log_unicode_boundaries() {
2701
+ for character in ['é', '—', '界', '🦀'] {
2702
+ let input = format!("{}{}tail", "a".repeat(99), character);
2703
+ for limit in 99..99 + character.len_utf8() {
2704
+ assert_eq!(
2705
+ truncate_for_log(&input, limit),
2706
+ format!("{}...", "a".repeat(99))
2707
+ );
2708
+ }
2709
+ let boundary = 99 + character.len_utf8();
2710
+ assert_eq!(
2711
+ truncate_for_log(&input, boundary),
2712
+ format!("{}{}...", "a".repeat(99), character)
2713
+ );
2714
+ }
2715
+ }
2716
+
2717
+ #[test]
2718
+ fn truncate_for_log_empty_and_zero_limit() {
2719
+ assert_eq!(truncate_for_log("", 0), "");
2720
+ assert_eq!(truncate_for_log("—", 0), "...");
2721
+ assert_eq!(truncate_for_log("—", 1), "...");
2722
+ assert_eq!(truncate_for_log("—", 3), "—");
2723
+ assert_eq!(truncate_for_log("hello", 5), "hello");
2724
+ }
2725
+
2726
#[test]
2727
fn gguf_model_config_qwen25_1_5b() {
2728
let cfg = GgufModelConfig::qwen25_1_5b();