Skip to content

Commit afea03d

Browse files
authored
Enable tracing of HTTP error bodies (Azure#2214)
We will not trace HTTP error bodies by default, but this causes issues with internal diagnostics. Resolves Azure#2213. Once Azure#682 is resolved, we could enable this de facto like in the Azure SDK for .NET.
1 parent 71d1d8d commit afea03d

File tree

3 files changed

+82
-17
lines changed

3 files changed

+82
-17
lines changed

sdk/core/azure_core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ hmac_rust = ["dep:sha2", "dep:hmac"]
5050
reqwest = ["typespec_client_core/reqwest"]
5151
reqwest_gzip = ["typespec_client_core/reqwest_gzip"]
5252
reqwest_rustls = ["typespec_client_core/reqwest_rustls"]
53-
test = []
53+
test = ["typespec_client_core/test"]
5454
tokio_fs = ["typespec_client_core/tokio_fs"]
5555
tokio_sleep = ["typespec_client_core/tokio_sleep"]
5656
xml = ["typespec_client_core/xml"]

sdk/typespec/typespec_client_core/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ json = ["typespec/json"]
5353
reqwest = ["reqwest/native-tls"]
5454
reqwest_gzip = ["reqwest/gzip"]
5555
reqwest_rustls = ["reqwest/rustls-tls-native-roots"]
56+
# Enables extra tracing including error bodies that may contain PII.
57+
test = []
5658
tokio_fs = ["tokio/fs", "tokio/sync", "tokio/io-util"]
5759
tokio_sleep = ["tokio/time"]
5860
xml = ["dep:quick-xml"]

sdk/typespec/typespec_client_core/src/error/http_error.rs

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
};
1111
use bytes::Bytes;
1212
use serde::Deserialize;
13-
use std::{collections::HashMap, fmt};
13+
use std::{collections::HashMap, fmt, str};
1414

1515
/// An HTTP error response.
1616
pub struct HttpError {
@@ -91,38 +91,90 @@ impl HttpError {
9191
}
9292
}
9393

94+
struct Unquote<'a>(&'a str);
95+
impl fmt::Debug for Unquote<'_> {
96+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97+
f.write_str(self.0)
98+
}
99+
}
100+
94101
impl fmt::Debug for HttpError {
95102
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96103
// Elide potential PII since it's too easily to accidentally leak through Debug or Display.
97-
f.debug_struct("HttpError")
104+
let mut dbg = f.debug_struct("HttpError");
105+
106+
#[cfg_attr(not(feature = "test"), allow(unused_mut))]
107+
let mut dbg = dbg
98108
.field("status", &self.status)
99-
.field("details", &self.details)
100-
.finish_non_exhaustive()
109+
.field("details", &self.details);
110+
111+
#[cfg(feature = "test")]
112+
{
113+
dbg = dbg.field(
114+
"body",
115+
&Unquote(
116+
String::from_utf8(self.body.to_vec())
117+
.as_deref()
118+
.unwrap_or("(bytes)"),
119+
),
120+
);
121+
}
122+
123+
dbg.finish_non_exhaustive()
101124
}
102125
}
103126

104127
impl fmt::Display for HttpError {
105128
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106-
struct Unquote<'a>(&'a str);
107-
impl fmt::Debug for Unquote<'_> {
129+
struct Status(StatusCode);
130+
impl fmt::Debug for Status {
108131
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109-
f.write_str(self.0)
132+
f.write_fmt(format_args!(
133+
"{} ({})",
134+
std::convert::Into::<u16>::into(self.0),
135+
self.0.canonical_reason()
136+
))
110137
}
111138
}
112139

113140
// Elide potential PII since it's too easily to accidentally leak through Debug or Display.
114-
f.debug_struct("HttpError")
115-
.field("Status", &Unquote(&self.status.to_string()))
141+
let mut dbg = f.debug_struct("HttpError");
142+
143+
#[cfg_attr(not(feature = "test"), allow(unused_mut))]
144+
let mut dbg = dbg
145+
.field("Status", &Status(self.status))
116146
.field(
117147
"Error Code",
118148
&Unquote(
119149
self.details
120150
.code
121151
.as_deref()
122-
.unwrap_or("(unknown error code)"),
152+
.unwrap_or("(error code unavailable)"),
123153
),
124154
)
125-
.finish_non_exhaustive()
155+
.field(
156+
"Message",
157+
&Unquote(
158+
self.details
159+
.message
160+
.as_deref()
161+
.unwrap_or("(message unavailable)"),
162+
),
163+
);
164+
165+
#[cfg(feature = "test")]
166+
{
167+
dbg = dbg.field(
168+
"Body",
169+
&Unquote(
170+
String::from_utf8(self.body.to_vec())
171+
.as_deref()
172+
.unwrap_or("(bytes)"),
173+
),
174+
);
175+
}
176+
177+
dbg.finish_non_exhaustive()
126178
}
127179
}
128180

@@ -228,11 +280,16 @@ mod tests {
228280
("x-ms-request-id".to_string(), "abcd1234".to_string()),
229281
]),
230282
};
231-
let actual = format!("{err:?}");
283+
#[cfg(not(feature = "test"))]
232284
assert_eq!(
233-
actual,
285+
format!("{err:?}"),
234286
r#"HttpError { status: NotFound, details: ErrorDetails { code: Some("Not Found"), message: Some("Resource not found") }, .. }"#
235287
);
288+
#[cfg(feature = "test")]
289+
assert_eq!(
290+
format!("{err:?}"),
291+
r#"HttpError { status: NotFound, details: ErrorDetails { code: Some("Not Found"), message: Some("Resource not found") }, body: resource not found, .. }"#
292+
);
236293
}
237294

238295
#[test]
@@ -249,10 +306,16 @@ mod tests {
249306
("x-ms-request-id".to_string(), "abcd1234".to_string()),
250307
]),
251308
};
252-
let actual = format!("{err}");
309+
#[cfg(not(feature = "test"))]
310+
assert_eq!(
311+
format!("{err:}"),
312+
r#"HttpError { Status: 404 (Not Found), Error Code: (error code unavailable), Message: (message unavailable), .. }"#
313+
);
314+
315+
#[cfg(feature = "test")]
253316
assert_eq!(
254-
actual,
255-
r#"HttpError { Status: 404, Error Code: (unknown error code), .. }"#
317+
format!("{err:}"),
318+
r#"HttpError { Status: 404 (Not Found), Error Code: (error code unavailable), Message: (message unavailable), Body: resource not found, .. }"#
256319
);
257320
}
258321

0 commit comments

Comments
 (0)