Skip to content

Commit 119a714

Browse files
authored
Add event logging (#1092)
* Currently only the `auth` CLI subcommand will log any request details when debug logging is enabled. Adding debug logging for all requests will enable users to troubleshoot issues on any API endpoint. To do this, we use the newly added exec hook feature in Progenitor to capture the details of the API call. In addition to the log event, we also create a `Span`. This is ignored by the CLI, but may be useful for other consumers of the SDK that want to integrate it with their monitoring infrastructure. An example event: { "timestamp": "2025-05-07T19:51:02.464942Z", "level": "DEBUG", "message": "request succeeded", "url": "https://oxide.sys.r3.oxide-preview.com/v1/instances?project=will", "path": "/v1/instances", "operation_id": "instance_list", "remote_addr": "45.154.216.35:443", "http.request.method": "GET", "http.response.content_length": 2377, "http.response.status_code": 200, "start_time": "2025-05-07T19:51:02.079228Z", "duration_ms": 385, "oxide.request_id": "1ff7d872-3a14-4604-8392-1e9d6e2ba8fd", "target": "oxide" } Closes #1014
1 parent ca173a5 commit 119a714

8 files changed

Lines changed: 318 additions & 29 deletions

File tree

Cargo.lock

Lines changed: 122 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ thouart = { git = "https://github.com/oxidecomputer/thouart" }
6363
tokio = { version = "1.44.2", features = ["full"] }
6464
toml = "0.8.22"
6565
toml_edit = "0.22.24"
66+
tracing = "0.1.41"
67+
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] }
6668
url = "2.5.4"
6769
uuid = { version = "1.16.0", features = ["serde", "v4"] }
6870

cli/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ thouart = { workspace = true }
4949
toml = { workspace = true }
5050
toml_edit = { workspace = true }
5151
tokio = { workspace = true }
52+
tracing = { workspace = true }
53+
tracing-subscriber = { workspace = true }
5254
url = { workspace = true }
5355
uuid = { workspace = true }
5456

cli/src/cli_builder.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
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::{any::TypeId, collections::BTreeMap, marker::PhantomData, net::IpAddr, path::PathBuf};
88

99
use anyhow::{bail, Result};
1010
use async_trait::async_trait;
1111
use clap::{Arg, ArgMatches, Command, CommandFactory, FromArgMatches};
12-
use log::LevelFilter;
12+
use tracing_subscriber::EnvFilter;
1313

1414
use crate::{
1515
context::Context,
@@ -229,11 +229,20 @@ impl<'a> NewCli<'a> {
229229
timeout,
230230
} = OxideCli::from_arg_matches(&matches).expect("failed to parse OxideCli from args");
231231

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

238247
let mut client_config = ClientConfig::default();
239248

cli/tests/test_auth.rs

Lines changed: 51 additions & 9 deletions
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},
@@ -14,6 +14,7 @@ use expectorate::assert_contents;
1414
use httpmock::{Method::POST, Mock, MockServer};
1515
use oxide::types::CurrentUser;
1616
use oxide_httpmock::MockServerExt;
17+
use predicates::prelude::*;
1718
use predicates::str;
1819
use serde_json::json;
1920

@@ -572,17 +573,58 @@ fn test_cmd_auth_status_env() {
572573

573574
#[test]
574575
fn test_cmd_auth_debug_logging() {
575-
let bad_url = "sys.oxide.invalid";
576+
let server = MockServer::start();
576577

577-
// Validate debug logs are printed
578-
Command::cargo_bin("oxide")
578+
let oxide_mock = server.current_user_view(|when, then| {
579+
when.into_inner()
580+
.header("authorization", "Bearer oxide-token-good");
581+
582+
then.ok(&oxide::types::CurrentUser {
583+
display_name: "privileged".to_string(),
584+
id: "001de000-05e4-4000-8000-000000004007".parse().unwrap(),
585+
silo_id: "d1bb398f-872c-438c-a4c6-2211e2042526".parse().unwrap(),
586+
silo_name: "funky-town".parse().unwrap(),
587+
});
588+
});
589+
590+
let cmd = Command::cargo_bin("oxide")
579591
.unwrap()
580592
.arg("--debug")
581593
.arg("auth")
582-
.arg("login")
583-
.arg("--host")
584-
.arg(bad_url)
594+
.arg("status")
595+
.env("OXIDE_HOST", server.url(""))
596+
.env("OXIDE_TOKEN", "oxide-token-good")
585597
.assert()
586-
.failure()
587-
.stderr(str::contains("DEBUG"));
598+
.success();
599+
600+
let stderr_str = std::str::from_utf8(&cmd.get_output().stderr).unwrap();
601+
assert!(str::is_match(r#""level":"DEBUG""#)
602+
.unwrap()
603+
.eval(stderr_str));
604+
assert!(str::is_match(r#""message":"request succeeded""#)
605+
.unwrap()
606+
.eval(stderr_str));
607+
assert!(str::is_match(r#""url":"http://127.0.0.1:\d+/v1/me""#)
608+
.unwrap()
609+
.eval(stderr_str));
610+
assert!(str::is_match(r#""path":"/v1/me""#)
611+
.unwrap()
612+
.eval(stderr_str));
613+
assert!(str::is_match(r#""remote_addr":"127.0.0.1:\d+""#)
614+
.unwrap()
615+
.eval(stderr_str));
616+
assert!(str::is_match(r#""http.request.method":"GET""#)
617+
.unwrap()
618+
.eval(stderr_str));
619+
assert!(str::is_match(r#""http.response.content_length":\d+"#)
620+
.unwrap()
621+
.eval(stderr_str));
622+
assert!(str::is_match(r#""http.response.status_code":200"#)
623+
.unwrap()
624+
.eval(stderr_str));
625+
assert!(str::is_match(r#""duration_ms":\d+"#)
626+
.unwrap()
627+
.eval(stderr_str));
628+
629+
oxide_mock.assert();
588630
}

0 commit comments

Comments
 (0)