Skip to content

Commit 5a151b4

Browse files
committed
Add detailed request logging
Currently we don't offer a way to expose the details of the API requests the CLI or other SDK consumers to users, making troubleshooting difficult. With the new `middleware` feature available in Progenitor, we can now inject our own logger using the `reqwest-tracing` crate. This gives us output like: ./target/debug/oxide --debug disk list --project will | jq . { "timestamp": "2025-02-26T17:29:23.354297Z", "level": "DEBUG", "fields": { "message": "close", "time.busy": "16.7ms", "time.idle": "365ms" }, "target": "oxide::tracing", "span": { "host": "oxide.sys.r3.oxide-preview.com", "http.request.method": "GET", "http.response.content_length": 998, "http.response.status_code": 200, "oxide.request_id": "c5e7d65e-bcb2-4ade-a817-6f13b681b19b", "url": "https://oxide.sys.r3.oxide-preview.com/v1/disks?project=will", "name": "Oxide API Request" }, "spans": [] } We will also log the first KiB of the request body, if present. This should be enough to capture the details human-readable requests, e.g. an OxQL query, but avoid too much noise from something like a disk import. The `--debug` flag will enable debug logs for both the CLI and any dependencies, such as `hyper`. To view only CLI logs, set `RUST_LOG=oxide=debug`. Closes #1014.
1 parent 077ec52 commit 5a151b4

13 files changed

+393
-124
lines changed

Cargo.lock

+218-88
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+6-2
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ crossterm = { version = "0.27.0", features = [ "event-stream" ] }
2323
dialoguer = "0.10.4"
2424
dirs = "4.0.0"
2525
dropshot = "0.13.0"
26-
env_logger = "0.10.2"
2726
expectorate = { version = "1.1.0", features = ["predicates"] }
2827
flume = "0.11.1"
2928
futures = "0.3.31"
29+
http = "1.2.0"
3030
httpmock = "0.7.0"
3131
humantime = "2"
32+
hyper = "1.5.2"
3233
indicatif = "0.17"
3334
libc = "0.2.169"
34-
log = "0.4.25"
3535
md5 = "0.7.0"
3636
newline-converter = "0.3.0"
3737
oauth2 = "5.0.0"
@@ -49,6 +49,8 @@ rcgen = "0.10.0"
4949
regex = "1.11.1"
5050
regress = "0.10.3"
5151
reqwest = "0.12.12"
52+
reqwest-middleware = { version = "0.4.1", features = ["json"] }
53+
reqwest-tracing = { version = "0.5.6" }
5254
rustfmt-wrapper = "0.2.1"
5355
schemars = { version = "0.8.20", features = ["chrono", "uuid1"] }
5456
serde = { version = "1.0.217", features = ["derive"] }
@@ -62,6 +64,8 @@ thouart = { git = "https://github.com/oxidecomputer/thouart" }
6264
tokio = { version = "1.43.0", features = ["full"] }
6365
toml = "0.8.20"
6466
toml_edit = "0.22.24"
67+
tracing = "0.1.41"
68+
tracing-subscriber = { version = "0.3", features = ["env-filter","json"] }
6569
url = "2.5.4"
6670
uuid = { version = "1.13.1", features = ["serde", "v4"] }
6771

cli/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@ colored = { workspace = true }
2828
crossterm = { workspace = true }
2929
dialoguer = { workspace = true }
3030
dirs = { workspace = true }
31-
env_logger = { workspace = true }
3231
futures = { workspace = true }
3332
humantime = { workspace = true }
3433
indicatif = { workspace = true }
35-
log = { workspace = true }
3634
md5 = { workspace = true }
3735
oauth2 = { workspace = true }
3836
open = { workspace = true }
@@ -49,6 +47,8 @@ thouart = { workspace = true }
4947
toml = { workspace = true }
5048
toml_edit = { workspace = true }
5149
tokio = { workspace = true }
50+
tracing = { workspace = true }
51+
tracing-subscriber = { workspace = true }
5252
url = { workspace = true }
5353
uuid = { workspace = true }
5454

cli/src/cli_builder.rs

+18-8
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
// Copyright 2024 Oxide Computer Company
5+
// Copyright 2025 Oxide Computer Company
66

7-
use std::{any::TypeId, collections::BTreeMap, marker::PhantomData, net::IpAddr, path::PathBuf};
7+
use std::{
8+
any::TypeId, collections::BTreeMap, io, marker::PhantomData, net::IpAddr, path::PathBuf,
9+
};
810

911
use anyhow::{bail, Result};
1012
use async_trait::async_trait;
1113
use clap::{Arg, ArgMatches, Command, CommandFactory, FromArgMatches};
12-
use log::LevelFilter;
14+
use tracing_subscriber::fmt::format::FmtSpan;
15+
use tracing_subscriber::EnvFilter;
1316

1417
use crate::{
1518
context::Context,
@@ -229,11 +232,18 @@ impl<'a> NewCli<'a> {
229232
timeout,
230233
} = OxideCli::from_arg_matches(&matches).expect("failed to parse OxideCli from args");
231234

232-
let mut log_builder = env_logger::builder();
233-
if debug {
234-
log_builder.filter_level(LevelFilter::Debug);
235-
}
236-
log_builder.init();
235+
let env_filter = if debug {
236+
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug"))
237+
} else {
238+
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))
239+
};
240+
241+
tracing_subscriber::fmt()
242+
.with_env_filter(env_filter)
243+
.with_span_events(FmtSpan::CLOSE)
244+
.with_writer(io::stderr)
245+
.json()
246+
.init();
237247

238248
let mut client_config = ClientConfig::default();
239249

cli/src/cmd_auth.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
// Copyright 2024 Oxide Computer Company
5+
// Copyright 2025 Oxide Computer Company
66

77
use std::error::Error;
88
use std::fs::{File, OpenOptions};
@@ -465,11 +465,11 @@ impl CmdAuthStatus {
465465

466466
match result {
467467
Ok(user) => {
468-
log::debug!("success response for {} (env): {:?}", host_env, user);
468+
tracing::debug!("success response for {} (env): {:?}", host_env, user);
469469
println_nopipe!("Logged in to {} as {}", host_env, user.id)
470470
}
471471
Err(e) => {
472-
log::debug!("error response for {} (env): {:#}", host_env, e);
472+
tracing::debug!("error response for {} (env): {:#}", host_env, e);
473473
println_nopipe!("{}: {}", host_env, Self::error_msg(&e))
474474
}
475475
};
@@ -490,11 +490,11 @@ impl CmdAuthStatus {
490490

491491
let status = match result {
492492
Ok(v) => {
493-
log::debug!("success response for {}: {:?}", profile_info.host, v);
493+
tracing::debug!("success response for {}: {:?}", profile_info.host, v);
494494
"Authenticated".to_string()
495495
}
496496
Err(e) => {
497-
log::debug!("error response for {}: {:#}", profile_info.host, e);
497+
tracing::debug!("error response for {}: {:#}", profile_info.host, e);
498498
Self::error_msg(&e)
499499
}
500500
};
@@ -566,6 +566,9 @@ impl CmdAuthStatus {
566566
// didn't supply all required values.
567567
format!("Internal error: {}", msg)
568568
}
569+
oxide::Error::MiddlewareError(e) => {
570+
format!("Middleware error: {}", e)
571+
}
569572
oxide::Error::InvalidUpgrade(_) => {
570573
unreachable!("auth should not be establishing a websocket")
571574
}

cli/tests/test_auth.rs

+33-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
// Copyright 2024 Oxide Computer Company
5+
// Copyright 2025 Oxide Computer Company
66

77
use std::{
88
fs::{read_to_string, write, File},
@@ -586,3 +586,35 @@ fn test_cmd_auth_debug_logging() {
586586
.failure()
587587
.stderr(str::contains("DEBUG"));
588588
}
589+
590+
#[test]
591+
fn test_cmd_auth_debug_trace_span() {
592+
let server = MockServer::start();
593+
594+
let oxide_mock = server.current_user_view(|when, then| {
595+
when.into_inner()
596+
.header("authorization", "Bearer oxide-token-good");
597+
598+
then.ok(&oxide::types::CurrentUser {
599+
display_name: "privileged".to_string(),
600+
id: "001de000-05e4-4000-8000-000000004007".parse().unwrap(),
601+
silo_id: "d1bb398f-872c-438c-a4c6-2211e2042526".parse().unwrap(),
602+
silo_name: "funky-town".parse().unwrap(),
603+
});
604+
});
605+
606+
let debug_output = r#".*"target":"oxide::tracing","span":\{"host":"127.0.0.1","http.request.method":"GET","http.response.content_length":\d+,"http.response.status_code":200,"url":"http://127.0.0.1:\d+/v1/me","name":"Oxide API Request"\}.*"#;
607+
608+
Command::cargo_bin("oxide")
609+
.unwrap()
610+
.arg("auth")
611+
.arg("status")
612+
.env("RUST_LOG", "oxide=debug")
613+
.env("OXIDE_HOST", server.url(""))
614+
.env("OXIDE_TOKEN", "oxide-token-good")
615+
.assert()
616+
.success()
617+
.stderr(str::is_match(debug_output).unwrap());
618+
619+
oxide_mock.assert();
620+
}

cli/tests/test_net.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
// Copyright 2024 Oxide Computer Company
5+
// Copyright 2025 Oxide Computer Company
66

77
use assert_cmd::Command;
88
use chrono::prelude::*;
@@ -295,7 +295,7 @@ fn test_port_config() {
295295
then.ok(&switch1_qsfp0_view);
296296
});
297297

298-
env_logger::init();
298+
tracing_subscriber::fmt().init();
299299

300300
Command::cargo_bin("oxide")
301301
.unwrap()

sdk/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,22 @@ clap = { workspace = true, optional = true }
1414
dirs = { workspace = true }
1515
flume = { workspace = true }
1616
futures = { workspace = true }
17+
http = { workspace = true }
18+
hyper = { workspace = true }
1719
progenitor-client = { workspace = true }
1820
rand = { workspace = true }
1921
regress = { workspace = true }
2022
reqwest = { workspace = true, features = ["native-tls-vendored"] }
23+
reqwest-middleware = { workspace = true }
24+
reqwest-tracing = { workspace = true }
2125
serde = { workspace = true }
2226
serde_json = { workspace = true }
2327
schemars = { workspace = true }
2428
thiserror = { workspace = true }
2529
tokio = { workspace = true }
2630
toml = { workspace = true }
2731
toml_edit = { workspace = true }
32+
tracing = { workspace = true }
2833
uuid = { workspace = true }
2934

3035
[dev-dependencies]

sdk/src/auth.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
// Copyright 2024 Oxide Computer Company
5+
// Copyright 2025 Oxide Computer Company
66

77
use std::{
88
collections::BTreeMap,
99
net::{IpAddr, SocketAddr},
1010
path::{Path, PathBuf},
1111
};
1212

13+
use crate::tracing::RequestSpan;
1314
use crate::{Client, OxideAuthError};
1415
use reqwest::ClientBuilder;
1516
use serde::Deserialize;
@@ -173,12 +174,17 @@ impl Client {
173174
.collect(),
174175
);
175176

176-
Ok(Self::new_with_client(
177-
&host,
178-
client_builder
177+
let client = client_builder
178+
.build()
179+
.expect("failed to construct underlying client object");
180+
181+
let client = {
182+
reqwest_middleware::ClientBuilder::new(client)
183+
.with(reqwest_tracing::TracingMiddleware::<RequestSpan>::new())
179184
.build()
180-
.expect("failure to construct underlying client object"),
181-
))
185+
};
186+
187+
Ok(Self::new_with_client(&host, client))
182188
}
183189
}
184190

sdk/src/generated_sdk.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -51712,14 +51712,14 @@ pub mod types {
5171251712
/// Version: 20250212.0.0
5171351713
pub struct Client {
5171451714
pub(crate) baseurl: String,
51715-
pub(crate) client: reqwest::Client,
51715+
pub(crate) client: reqwest_middleware::ClientWithMiddleware,
5171651716
}
5171751717

5171851718
impl Client {
5171951719
/// Create a new client.
5172051720
///
5172151721
/// `baseurl` is the base URL provided to the internal
51722-
/// `reqwest::Client`, and should include a scheme and hostname,
51722+
/// HTTP client, and should include a scheme and hostname,
5172351723
/// as well as port and a path stem if applicable.
5172451724
pub fn new(baseurl: &str) -> Self {
5172551725
#[cfg(not(target_arch = "wasm32"))]
@@ -51731,16 +51731,21 @@ impl Client {
5173151731
};
5173251732
#[cfg(target_arch = "wasm32")]
5173351733
let client = reqwest::ClientBuilder::new();
51734-
Self::new_with_client(baseurl, client.build().unwrap())
51734+
let built_client = client.build().unwrap();
51735+
let built_client = reqwest_middleware::ClientBuilder::new(built_client).build();
51736+
Self::new_with_client(baseurl, built_client)
5173551737
}
5173651738

51737-
/// Construct a new client with an existing `reqwest::Client`,
51739+
/// Construct a new client with an existing HTTP client,
5173851740
/// allowing more control over its configuration.
5173951741
///
5174051742
/// `baseurl` is the base URL provided to the internal
51741-
/// `reqwest::Client`, and should include a scheme and hostname,
51743+
/// HTTP client, and should include a scheme and hostname,
5174251744
/// as well as port and a path stem if applicable.
51743-
pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self {
51745+
pub fn new_with_client(
51746+
baseurl: &str,
51747+
client: reqwest_middleware::ClientWithMiddleware,
51748+
) -> Self {
5174451749
Self {
5174551750
baseurl: baseurl.to_string(),
5174651751
client,
@@ -51752,8 +51757,8 @@ impl Client {
5175251757
&self.baseurl
5175351758
}
5175451759

51755-
/// Get the internal `reqwest::Client` used to make requests.
51756-
pub fn client(&self) -> &reqwest::Client {
51760+
/// Get the internal HTTP client used to make requests.
51761+
pub fn client(&self) -> &reqwest_middleware::ClientWithMiddleware {
5175751762
&self.client
5175851763
}
5175951764

sdk/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
// Copyright 2024 Oxide Computer Company
5+
// Copyright 2025 Oxide Computer Company
66

77
#![forbid(unsafe_code)]
88
#![doc = include_str!("../README.md")]
@@ -16,6 +16,7 @@ mod auth;
1616
mod clap_feature;
1717
pub mod extras;
1818
mod generated_sdk;
19+
mod tracing;
1920

2021
pub use auth::*;
2122
pub use generated_sdk::*;

0 commit comments

Comments
 (0)