Bound diagnostic payload allocation and trace provider restarts

This commit is contained in:
2026-09-23 16:05:32 -05:00
parent 3bfd7c0a54
commit ea7117cc66
4 changed files with 228 additions and 53 deletions
+55 -6
View File
@@ -189,7 +189,7 @@ pub(crate) fn sanitize_error(error: impl std::fmt::Display) -> String {
pub(crate) fn raw_model_payload_context<M>(
ctx: &ModelContext<M>,
raw_payload: impl AsRef<str>,
build_payload: impl FnOnce(usize) -> Value,
) -> Option<Value>
where
M: Entity,
@@ -203,10 +203,7 @@ where
}
let max_chars = (*settings.remote_logging_model_payload_max_chars.value()).max(1);
Some(tail_limited_payload_context(
raw_payload.as_ref(),
max_chars,
))
Some(build_payload(max_chars))
}
fn enrich_context(mut context: Value) -> Value {
@@ -290,7 +287,59 @@ fn truncate_chars(value: &str, max_chars: usize) -> String {
truncated
}
fn tail_limited_payload_context(payload: &str, max_chars: usize) -> Value {
/// Serialize directly into a bounded UTF-8 tail instead of materializing the full
/// request and its archive as a JSON Value and then another large String.
pub(crate) fn bounded_payload_context(
max_chars: usize,
serialize: impl FnOnce(&mut dyn std::io::Write) -> serde_json::Result<()>,
) -> Value {
struct TailWriter {
bytes: std::collections::VecDeque<u8>,
max_bytes: usize,
total_chars: usize,
}
impl std::io::Write for TailWriter {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
self.total_chars += bytes.iter().filter(|byte| **byte & 0xc0 != 0x80).count();
let retained = &bytes[bytes.len().saturating_sub(self.max_bytes)..];
let discard = (self.bytes.len() + retained.len()).saturating_sub(self.max_bytes);
self.bytes.drain(..discard);
self.bytes.extend(retained);
Ok(bytes.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut writer = TailWriter {
bytes: std::collections::VecDeque::new(),
max_bytes: max_chars.saturating_mul(4),
total_chars: 0,
};
if let Err(error) = serialize(&mut writer) {
return json!({ "serialization_error": sanitize_error(error) });
}
// A byte-limited tail can begin in the middle of a multibyte character.
while writer.bytes.front().is_some_and(|byte| byte & 0xc0 == 0x80) {
writer.bytes.pop_front();
}
let bytes = writer.bytes.into_iter().collect::<Vec<_>>();
let payload = String::from_utf8_lossy(&bytes);
let mut context = tail_limited_payload_context(&payload, max_chars);
context["payload_total_chars"] = json!(writer.total_chars);
context["truncated"] = json!(writer.total_chars > max_chars);
context["truncation_strategy"] = json!(if writer.total_chars > max_chars {
Some("tail")
} else {
None
});
context
}
pub(crate) fn tail_limited_payload_context(payload: &str, max_chars: usize) -> Value {
let total_chars = payload.chars().count();
let truncated = total_chars > max_chars;
let payload = if truncated {