Files
codex/codex-rs/feedback/src/metadata_tests.rs
T
Dmitriy Kovalenko a285066737 Preserve diagnostic logs when SQLite logging fails (#47886)
## Why

SQLite write failures can leave persisted logs incomplete, while log snapshots taken before collection miss errors raised during that collection.

## What changed

- Report SQLite flush failures directly to the in-memory log buffer without re-entering tracing, using an error category and batch size instead of raw error details.
- Track whether the log writer has lost a batch and make app-server diagnostic uploads use the memory buffer when SQLite logs are incomplete.
- Refresh log bytes after flushing and querying SQLite so the same submission includes collection failures, while preserving captured metadata and thread identity.

## Testing

Add regression tests for failure reporting without recursion, subsequent write recovery, metadata preservation, and healthy and failing SQLite collection. Add an upload integration test that captures attachments locally and checks failure diagnostics, memory-buffer fallback, and omission of raw SQLite error details.

GitOrigin-RevId: 51b0c4b69c977d9f4e74f2f245a9fd90fdb13761
2026-09-24 17:04:24 +00:00

104 lines
3.8 KiB
Rust

//! Exercise dynamic tag collection, upload serialization, and bounded updates.
use super::*;
use crate::CodexFeedback;
use pretty_assertions::assert_eq;
use sentry::protocol::Envelope;
use sentry::protocol::EnvelopeItem;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
#[test]
fn refreshing_logs_preserves_captured_metadata() {
let feedback = CodexFeedback::new();
let _guard = tracing_subscriber::registry()
.with(feedback.metadata_layer())
.set_default();
let thread_id = ThreadId::new();
tracing::info!(target: FEEDBACK_TAGS_TARGET, approval_policy = "on-request");
feedback.report_failure("initial log\n");
let mut snapshot = feedback.snapshot(Some(thread_id));
// Another conversation changes the process-wide tags while log collection runs.
tracing::info!(target: FEEDBACK_TAGS_TARGET, approval_policy = "never");
feedback.report_failure("flush failed\n");
snapshot.refresh_logs(&feedback);
assert_eq!(
(snapshot.tags, snapshot.thread_id, snapshot.bytes),
(
BTreeMap::from([("approval_policy".to_string(), "on-request".to_string())]),
thread_id.to_string(),
b"initial log\nflush failed\n".to_vec(),
)
);
}
#[test]
fn dynamic_tags_survive_upload_alongside_static_fields() {
let feedback = CodexFeedback::new();
let _guard = tracing_subscriber::registry()
.with(feedback.metadata_layer())
.set_default();
let mut expected: BTreeMap<_, _> = (0..200)
.map(|index| {
(
format!("feature.test_{index}"),
(index % 2 == 0).to_string(),
)
})
.collect();
expected.insert("model_context_window".to_string(), "872000".to_string());
tracing::info!(
target: FEEDBACK_TAGS_TARGET,
model = "test-model",
tags_json = ?tracing::field::display(serde_json::json!(expected)),
);
expected.insert("model".to_string(), "test-model".to_string());
let snapshot = feedback.snapshot(/*session_id*/ None);
assert_eq!(snapshot.tags, expected);
let event = snapshot.feedback_event(
"bug", /*reason*/ None, /*tags*/ None, /*session_source*/ None,
);
expected.extend([
("thread_id".to_string(), snapshot.thread_id),
("classification".to_string(), "bug".to_string()),
(
"cli_version".to_string(),
env!("CARGO_PKG_VERSION").to_string(),
),
]);
assert_eq!(event.tags, expected);
let mut envelope = Envelope::new();
envelope.add_item(EnvelopeItem::Event(event));
let (_, size) = crate::upload::encode_envelope(&envelope).unwrap();
assert!(size < crate::MAX_EVENT_BYTES);
}
#[test]
fn bounded_dynamic_tags_still_update_existing_values() {
let feedback = CodexFeedback::new();
let _guard = tracing_subscriber::registry()
.with(feedback.metadata_layer())
.set_default();
let mut expected: BTreeMap<_, _> = (0..MAX_FEEDBACK_TAGS)
.map(|index| (format!("feature.test_{index}"), "false".to_string()))
.collect();
tracing::info!(target: FEEDBACK_TAGS_TARGET, tags_json = %serde_json::json!(expected));
tracing::info!(
target: FEEDBACK_TAGS_TARGET,
tags_json = %serde_json::json!({
"feature.overflow": "true",
"feature.test_0": "true",
}),
);
expected.insert("feature.test_0".to_string(), "true".to_string());
assert_eq!(feedback.snapshot(/*session_id*/ None).tags, expected);
tracing::info!(
target: FEEDBACK_TAGS_TARGET,
tags_json = %serde_json::json!({"feature.test_0": "false"}),
);
expected.insert("feature.test_0".to_string(), "false".to_string());
assert_eq!(feedback.snapshot(/*session_id*/ None).tags, expected);
}