chore: improve client (#10149)

<img width="883" height="84" alt="Screenshot 2026-01-29 at 11 13 12"
src="https://github.com/user-attachments/assets/090a2fec-94ed-4c0f-aee5-1653ed8b1439"
/>
This commit is contained in:
jif-oai
2026-01-29 11:25:22 +01:00
committed by GitHub
parent 6a06726af2
commit 4ba911d48c
7 changed files with 43 additions and 33 deletions

1
codex-rs/Cargo.lock generated
View File

@@ -1855,6 +1855,7 @@ dependencies = [
"codex-otel",
"codex-protocol",
"dirs",
"owo-colors",
"pretty_assertions",
"serde",
"serde_json",

View File

@@ -11,6 +11,7 @@ clap = { workspace = true, features = ["derive", "env"] }
codex-otel = { workspace = true }
codex-protocol = { workspace = true }
dirs = { workspace = true }
owo-colors = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sqlx = { workspace = true }

View File

@@ -5,7 +5,6 @@ CREATE TABLE logs (
level TEXT NOT NULL,
target TEXT NOT NULL,
message TEXT,
fields_json TEXT NOT NULL,
module_path TEXT,
file TEXT,
line INTEGER

View File

@@ -9,6 +9,7 @@ use chrono::Utc;
use clap::Parser;
use codex_state::STATE_DB_FILENAME;
use dirs::home_dir;
use owo_colors::OwoColorize;
use sqlx::QueryBuilder;
use sqlx::Row;
use sqlx::Sqlite;
@@ -64,8 +65,6 @@ struct LogRow {
ts_nanos: i64,
level: String,
message: Option<String>,
fields_json: String,
module_path: Option<String>,
file: Option<String>,
line: Option<i64>,
}
@@ -237,7 +236,7 @@ async fn fetch_max_id(pool: &SqlitePool, filter: &LogFilter) -> anyhow::Result<i
fn base_select_builder<'a>() -> QueryBuilder<'a, Sqlite> {
QueryBuilder::<Sqlite>::new(
"SELECT id, ts, ts_nanos, level, message, fields_json, module_path, file, line FROM logs WHERE 1 = 1",
"SELECT id, ts, ts_nanos, level, message, file, line FROM logs WHERE 1 = 1",
)
}
@@ -269,19 +268,38 @@ fn push_filters<'a>(builder: &mut QueryBuilder<'a, Sqlite>, filter: &'a LogFilte
fn format_row(row: &LogRow) -> String {
let timestamp = format_timestamp(row.ts, row.ts_nanos);
let level = row.level.as_str();
let location = match (&row.file, row.line) {
(Some(file), Some(line)) => format!("{file}:{line}"),
(Some(file), None) => file.clone(),
_ => "-".to_string(),
};
let module = row.module_path.as_deref().unwrap_or("-");
let message = row.message.as_deref().unwrap_or("");
let fields = row.fields_json.as_str();
let level = row.level.as_str();
if fields == "{}" || fields.is_empty() {
return format!("{timestamp} {level:<5} [{module}] {location} - {message}");
let level_colored = color_level(level);
let timestamp_colored = timestamp.dimmed().to_string();
let location_colored = location.dimmed().to_string();
let message_colored = message.bold().to_string();
format!("{timestamp_colored} {level_colored} {location_colored} - {message_colored}")
}
fn color_level(level: &str) -> String {
let padded = format!("{level:<5}");
if level.eq_ignore_ascii_case("error") {
return padded.red().bold().to_string();
}
format!("{timestamp} {level:<5} [{module}] {location} - {message} {fields}")
if level.eq_ignore_ascii_case("warn") {
return padded.yellow().bold().to_string();
}
if level.eq_ignore_ascii_case("info") {
return padded.green().bold().to_string();
}
if level.eq_ignore_ascii_case("debug") {
return padded.blue().bold().to_string();
}
if level.eq_ignore_ascii_case("trace") {
return padded.magenta().bold().to_string();
}
padded.bold().to_string()
}
fn format_timestamp(ts: i64, ts_nanos: i64) -> String {

View File

@@ -22,7 +22,6 @@ use std::time::Duration;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use serde_json::Value;
use tokio::sync::mpsc;
use tracing::Event;
use tracing::field::Field;
@@ -54,7 +53,7 @@ where
{
fn on_event(&self, event: &Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
let metadata = event.metadata();
let mut visitor = JsonVisitor::default();
let mut visitor = MessageVisitor::default();
event.record(&mut visitor);
let now = SystemTime::now()
@@ -66,7 +65,6 @@ where
level: metadata.level().as_str().to_string(),
target: metadata.target().to_string(),
message: visitor.message,
fields_json: Value::Object(visitor.fields).to_string(),
module_path: metadata.module_path().map(ToString::to_string),
file: metadata.file().map(ToString::to_string),
line: metadata.line().map(|line| line as i64),
@@ -114,49 +112,44 @@ async fn flush(state_db: &std::sync::Arc<StateRuntime>, buffer: &mut Vec<LogEntr
}
#[derive(Default)]
struct JsonVisitor {
fields: serde_json::Map<String, Value>,
struct MessageVisitor {
message: Option<String>,
}
impl JsonVisitor {
fn record_value(&mut self, field: &Field, value: Value) {
impl MessageVisitor {
fn record_message(&mut self, field: &Field, value: String) {
if field.name() == "message" && self.message.is_none() {
self.message = Some(match &value {
Value::String(message) => message.clone(),
_ => value.to_string(),
});
self.message = Some(value);
}
self.fields.insert(field.name().to_string(), value);
}
}
impl Visit for JsonVisitor {
impl Visit for MessageVisitor {
fn record_i64(&mut self, field: &Field, value: i64) {
self.record_value(field, Value::from(value));
self.record_message(field, value.to_string());
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.record_value(field, Value::from(value));
self.record_message(field, value.to_string());
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.record_value(field, Value::from(value));
self.record_message(field, value.to_string());
}
fn record_f64(&mut self, field: &Field, value: f64) {
self.record_value(field, Value::from(value));
self.record_message(field, value.to_string());
}
fn record_str(&mut self, field: &Field, value: &str) {
self.record_value(field, Value::from(value));
self.record_message(field, value.to_string());
}
fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
self.record_value(field, Value::from(value.to_string()));
self.record_message(field, value.to_string());
}
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
self.record_value(field, Value::from(format!("{value:?}")));
self.record_message(field, format!("{value:?}"));
}
}

View File

@@ -7,7 +7,6 @@ pub struct LogEntry {
pub level: String,
pub target: String,
pub message: Option<String>,
pub fields_json: String,
pub module_path: Option<String>,
pub file: Option<String>,
pub line: Option<i64>,

View File

@@ -214,7 +214,7 @@ FROM threads
}
let mut builder = QueryBuilder::<Sqlite>::new(
"INSERT INTO logs (ts, ts_nanos, level, target, message, fields_json, module_path, file, line) ",
"INSERT INTO logs (ts, ts_nanos, level, target, message, module_path, file, line) ",
);
builder.push_values(entries, |mut row, entry| {
row.push_bind(entry.ts)
@@ -222,7 +222,6 @@ FROM threads
.push_bind(&entry.level)
.push_bind(&entry.target)
.push_bind(&entry.message)
.push_bind(&entry.fields_json)
.push_bind(&entry.module_path)
.push_bind(&entry.file)
.push_bind(entry.line);