Skip to content

Commit db4dfb4

Browse files
authored
Add logging to the uv build backend (#15533)
Add support for `RUST_LOG` to the uv build backend. While we were previously using logging statements in the uv build backend, they could only be shown when when using the direct build fast path through uv, as there was no tracing subscriber to write log messages out. This means no debug logging when using the build backend through pip, `python -m build`, an incompatible version of uv, or any other build frontend; No option to figure why includes and excludes behave the way they do. This PR closes this gap by adding a tracing subscriber. The only option to enable it is `RUST_LOG`, as we don't have a CLI. The formatting style is the same as for uv, and color is also support in the same way, albeit only through anstream's support for TTYs and environment variables. We recommend only `RUST_LOG=uv=debug` and `RUST_LOG=uv=verbose` in the docs, but this can be used to debug into crates such as `glob`, too. <img width="1008" height="325" alt="image" src="https://github.com/user-attachments/assets/d33df219-750b-46a2-b3b4-8895aa137ab9" /> **Before** ``` $ pip wheel . -v [...] Looking in links: /home/konsti/projects/uv/target/wheels/ Processing /home/konsti/projects/uv/scripts/packages/built-by-uv Running command pip subprocess to install build dependencies Looking in links: /home/konsti/projects/uv/target/wheels/ Processing /home/konsti/projects/uv/target/wheels/uv_build-0.8.13-py3-none-manylinux_2_39_x86_64.whl Installing collected packages: uv_build Successfully installed uv_build-0.8.13 Installing build dependencies ... done Running command Getting requirements to build wheel Getting requirements to build wheel ... done Running command Preparing metadata (pyproject.toml) Preparing metadata (pyproject.toml) ... done Building wheels for collected packages: built-by-uv Running command Building wheel for built-by-uv (pyproject.toml) Error: Unsupported glob expression in: `tool.uv.build-backend.*-exclude` Caused by: Invalid character `!` at position 10 in glob: `**/build-*!$§%!½¼²¼³¬!§%$§%.h`. hint: Characters can be escaped with a backslash error: subprocess-exited-with-error × Building wheel for built-by-uv (pyproject.toml) did not run successfully. │ exit code: 1 ╰─> See above for output. note: This error originates from a subprocess, and is likely not a problem with pip. full command: /usr/bin/python3 /usr/lib/python3/dist-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py build_wheel /tmp/tmpow1illc9 cwd: /home/konsti/projects/uv/scripts/packages/built-by-uv Building wheel for built-by-uv (pyproject.toml) ... error ERROR: Failed building wheel for built-by-uv Failed to build built-by-uv ERROR: Failed to build one or more wheels ``` **After** ``` $ RUST_LOG=uv=debug pip wheel . -v [...] Looking in links: /home/konsti/projects/uv/target/wheels/ Processing /home/konsti/projects/uv/scripts/packages/built-by-uv Running command pip subprocess to install build dependencies Looking in links: /home/konsti/projects/uv/target/wheels/ Processing /home/konsti/projects/uv/target/wheels/uv_build-0.8.13-py3-none-manylinux_2_39_x86_64.whl Installing collected packages: uv_build Successfully installed uv_build-0.8.13 Installing build dependencies ... done Running command Getting requirements to build wheel Getting requirements to build wheel ... done Running command Preparing metadata (pyproject.toml) DEBUG Writing metadata files to /tmp/pip-modern-metadata-l_kh78cj DEBUG Found PEP 639 license declarations, using METADATA 2.4 DEBUG License files match: `LICENSE-APACHE` DEBUG License files match: `LICENSE-MIT` DEBUG License files match: `third-party-licenses/PEP-401.txt` Preparing metadata (pyproject.toml) ... done Building wheels for collected packages: built-by-uv Running command Building wheel for built-by-uv (pyproject.toml) DEBUG Checking metadata directory /tmp/pip-modern-metadata-l_kh78cj/built_by_uv-0.1.0.dist-info DEBUG Found PEP 639 license declarations, using METADATA 2.4 DEBUG License files match: `LICENSE-APACHE` DEBUG License files match: `LICENSE-MIT` DEBUG License files match: `third-party-licenses/PEP-401.txt` DEBUG Writing wheel at /tmp/pip-wheel-bu6to9i7/built_by_uv-0.1.0-py3-none-any.whl DEBUG Wheel excludes: ["__pycache__", "*.pyc", "*.pyo", "build-*!$§%!½¼²¼³¬!§%$§%.h", "/src/built_by_uv/not-packaged.txt"] Error: Unsupported glob expression in: `tool.uv.build-backend.*-exclude` Caused by: Invalid character `!` at position 10 in glob: `**/build-*!$§%!½¼²¼³¬!§%$§%.h`. hint: Characters can be escaped with a backslash error: subprocess-exited-with-error × Building wheel for built-by-uv (pyproject.toml) did not run successfully. │ exit code: 1 ╰─> See above for output. note: This error originates from a subprocess, and is likely not a problem with pip. full command: /usr/bin/python3 /usr/lib/python3/dist-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py build_wheel /tmp/tmpjrxou13a cwd: /home/konsti/projects/uv/scripts/packages/built-by-uv Building wheel for built-by-uv (pyproject.toml) ... error ERROR: Failed building wheel for built-by-uv Failed to build built-by-uv ERROR: Failed to build one or more wheels ``` (There is no color in the above uv log statements, as pip doesn't register as a TTY) Fixes #12723
1 parent 2eb1c72 commit db4dfb4

File tree

10 files changed

+171
-96
lines changed

10 files changed

+171
-96
lines changed

Cargo.lock

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ uv-git-types = { path = "crates/uv-git-types" }
4343
uv-globfilter = { path = "crates/uv-globfilter" }
4444
uv-install-wheel = { path = "crates/uv-install-wheel", default-features = false }
4545
uv-installer = { path = "crates/uv-installer" }
46+
uv-logging = { path = "crates/uv-logging" }
4647
uv-macros = { path = "crates/uv-macros" }
4748
uv-metadata = { path = "crates/uv-metadata" }
4849
uv-normalize = { path = "crates/uv-normalize" }
@@ -180,7 +181,7 @@ toml = { version = "0.9.2", features = ["fast_hash"] }
180181
toml_edit = { version = "0.23.2", features = ["serde"] }
181182
tracing = { version = "0.1.40" }
182183
tracing-durations-export = { version = "0.3.0", features = ["plot"] }
183-
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry"] }
184+
tracing-subscriber = { version = "0.3.18" } # Default feature set for uv_build, uv activates extra features
184185
tracing-test = { version = "0.2.5" }
185186
tracing-tree = { version = "0.4.0" }
186187
unicode-width = { version = "0.2.0" }

crates/uv-build/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ license = { workspace = true }
1111

1212
[dependencies]
1313
uv-build-backend = { workspace = true }
14+
uv-logging = { workspace = true }
1415
uv-version = { workspace = true }
1516

17+
anstream = { workspace = true }
1618
anyhow = { workspace = true }
19+
tracing-subscriber = { workspace = true, features = ["env-filter"] }
1720

1821
[lints]
1922
workspace = true

crates/uv-build/src/main.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
1-
use anyhow::{Context, Result, bail};
21
use std::env;
32
use std::io::Write;
43
use std::path::PathBuf;
54

5+
use anyhow::{Context, Result, bail};
6+
use tracing_subscriber::filter::LevelFilter;
7+
use tracing_subscriber::layer::SubscriberExt;
8+
use tracing_subscriber::util::SubscriberInitExt;
9+
use tracing_subscriber::{EnvFilter, Layer};
10+
11+
use uv_logging::UvFormat;
12+
613
/// Entrypoint for the `uv-build` Python package.
714
fn main() -> Result<()> {
15+
// Support configuring the log level with `RUST_LOG` (shows only the error level by default) and
16+
// color.
17+
//
18+
// This configuration is a simplified version of the uv logging configuration. When using
19+
// uv_build through uv proper, the uv logging configuration applies.
20+
let filter = EnvFilter::builder()
21+
.with_default_directive(LevelFilter::OFF.into())
22+
.from_env()
23+
.context("Invalid RUST_LOG directives")?;
24+
let stderr_layer = tracing_subscriber::fmt::layer()
25+
.event_format(UvFormat::default())
26+
.with_writer(std::sync::Mutex::new(anstream::stderr()))
27+
.with_filter(filter);
28+
tracing_subscriber::registry().with(stderr_layer).init();
29+
830
// Handrolled to avoid the large clap dependency
931
let mut args = env::args_os();
1032
// Skip the name of the binary

crates/uv-dev/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ tokio = { workspace = true }
5959
tokio-util = { workspace = true }
6060
tracing = { workspace = true }
6161
tracing-durations-export = { workspace = true, features = ["plot"] }
62-
tracing-subscriber = { workspace = true }
62+
tracing-subscriber = { workspace = true, features = ["env-filter"] }
6363
uv-performance-memory-allocator = { path = "../uv-performance-memory-allocator", optional = true }
6464
walkdir = { workspace = true }
6565

crates/uv-logging/Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "uv-logging"
3+
version = "0.0.1"
4+
edition = { workspace = true }
5+
rust-version = { workspace = true }
6+
homepage = { workspace = true }
7+
documentation = { workspace = true }
8+
repository = { workspace = true }
9+
authors = { workspace = true }
10+
license = { workspace = true }
11+
12+
[lib]
13+
doctest = false
14+
15+
[lints]
16+
workspace = true
17+
18+
[dependencies]
19+
jiff = { workspace = true }
20+
owo-colors = { workspace = true }
21+
tracing-subscriber = { workspace = true }
22+
tracing = { workspace = true }

crates/uv-logging/src/lib.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use std::fmt;
2+
3+
use jiff::Timestamp;
4+
use owo_colors::OwoColorize;
5+
use tracing::{Event, Subscriber};
6+
use tracing_subscriber::fmt::format::Writer;
7+
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
8+
use tracing_subscriber::registry::LookupSpan;
9+
10+
/// The style of a uv logging line.
11+
pub struct UvFormat {
12+
pub display_timestamp: bool,
13+
pub display_level: bool,
14+
pub show_spans: bool,
15+
}
16+
17+
impl Default for UvFormat {
18+
/// Regardless of the tracing level, show messages without any adornment.
19+
fn default() -> Self {
20+
Self {
21+
display_timestamp: false,
22+
display_level: true,
23+
show_spans: false,
24+
}
25+
}
26+
}
27+
28+
/// See <https://docs.rs/tracing-subscriber/0.3.18/src/tracing_subscriber/fmt/format/mod.rs.html#1026-1156>
29+
impl<S, N> FormatEvent<S, N> for UvFormat
30+
where
31+
S: Subscriber + for<'a> LookupSpan<'a>,
32+
N: for<'a> FormatFields<'a> + 'static,
33+
{
34+
fn format_event(
35+
&self,
36+
ctx: &FmtContext<'_, S, N>,
37+
mut writer: Writer<'_>,
38+
event: &Event<'_>,
39+
) -> fmt::Result {
40+
let meta = event.metadata();
41+
let ansi = writer.has_ansi_escapes();
42+
43+
if self.display_timestamp {
44+
if ansi {
45+
write!(writer, "{} ", Timestamp::now().dimmed())?;
46+
} else {
47+
write!(writer, "{} ", Timestamp::now())?;
48+
}
49+
}
50+
51+
if self.display_level {
52+
let level = meta.level();
53+
// Same colors as tracing
54+
if ansi {
55+
match *level {
56+
tracing::Level::TRACE => write!(writer, "{} ", level.purple())?,
57+
tracing::Level::DEBUG => write!(writer, "{} ", level.blue())?,
58+
tracing::Level::INFO => write!(writer, "{} ", level.green())?,
59+
tracing::Level::WARN => write!(writer, "{} ", level.yellow())?,
60+
tracing::Level::ERROR => write!(writer, "{} ", level.red())?,
61+
}
62+
} else {
63+
write!(writer, "{level} ")?;
64+
}
65+
}
66+
67+
if self.show_spans {
68+
let span = event.parent();
69+
let mut seen = false;
70+
71+
let span = span
72+
.and_then(|id| ctx.span(id))
73+
.or_else(|| ctx.lookup_current());
74+
75+
let scope = span.into_iter().flat_map(|span| span.scope().from_root());
76+
77+
for span in scope {
78+
seen = true;
79+
if ansi {
80+
write!(writer, "{}:", span.metadata().name().bold())?;
81+
} else {
82+
write!(writer, "{}:", span.metadata().name())?;
83+
}
84+
}
85+
86+
if seen {
87+
writer.write_char(' ')?;
88+
}
89+
}
90+
91+
ctx.field_format().format_fields(writer.by_ref(), event)?;
92+
93+
writeln!(writer)
94+
}
95+
}

crates/uv/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ uv-git = { workspace = true }
3535
uv-git-types = { workspace = true }
3636
uv-install-wheel = { workspace = true, default-features = false }
3737
uv-installer = { workspace = true }
38+
uv-logging = { workspace = true }
3839
uv-normalize = { workspace = true }
3940
uv-pep440 = { workspace = true }
4041
uv-pep508 = { workspace = true }
@@ -82,7 +83,6 @@ indicatif = { workspace = true }
8283
indoc = { workspace = true }
8384
itertools = { workspace = true }
8485
h2 = { workspace = true }
85-
jiff = { workspace = true }
8686
miette = { workspace = true, features = ["fancy-no-backtrace"] }
8787
owo-colors = { workspace = true }
8888
petgraph = { workspace = true }
@@ -102,7 +102,7 @@ toml = { workspace = true }
102102
toml_edit = { workspace = true }
103103
tracing = { workspace = true }
104104
tracing-durations-export = { workspace = true, features = ["plot"], optional = true }
105-
tracing-subscriber = { workspace = true, features = ["json"] }
105+
tracing-subscriber = { workspace = true, features = ["env-filter", "json", "registry"] }
106106
tracing-tree = { workspace = true }
107107
unicode-width = { workspace = true }
108108
url = { workspace = true }

crates/uv/src/logging.rs

Lines changed: 2 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
1-
use std::fmt;
21
use std::str::FromStr;
32

43
use anyhow::Context;
5-
use jiff::Timestamp;
6-
use owo_colors::OwoColorize;
7-
use tracing::{Event, Subscriber};
84
#[cfg(feature = "tracing-durations-export")]
95
use tracing_durations_export::{
106
DurationsLayer, DurationsLayerBuilder, DurationsLayerDropGuard, plot::PlotConfig,
117
};
128
use tracing_subscriber::filter::Directive;
13-
use tracing_subscriber::fmt::format::Writer;
14-
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
159
use tracing_subscriber::layer::SubscriberExt;
16-
use tracing_subscriber::registry::LookupSpan;
1710
use tracing_subscriber::util::SubscriberInitExt;
1811
use tracing_subscriber::{EnvFilter, Layer, Registry};
1912
use tracing_tree::HierarchicalLayer;
2013
use tracing_tree::time::Uptime;
2114

2215
use uv_cli::ColorChoice;
16+
use uv_logging::UvFormat;
2317
use uv_static::EnvVars;
2418

2519
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
@@ -31,81 +25,6 @@ pub(crate) enum Level {
3125
TraceAll,
3226
}
3327

34-
struct UvFormat {
35-
display_timestamp: bool,
36-
display_level: bool,
37-
show_spans: bool,
38-
}
39-
40-
/// See <https://docs.rs/tracing-subscriber/0.3.18/src/tracing_subscriber/fmt/format/mod.rs.html#1026-1156>
41-
impl<S, N> FormatEvent<S, N> for UvFormat
42-
where
43-
S: Subscriber + for<'a> LookupSpan<'a>,
44-
N: for<'a> FormatFields<'a> + 'static,
45-
{
46-
fn format_event(
47-
&self,
48-
ctx: &FmtContext<'_, S, N>,
49-
mut writer: Writer<'_>,
50-
event: &Event<'_>,
51-
) -> fmt::Result {
52-
let meta = event.metadata();
53-
let ansi = writer.has_ansi_escapes();
54-
55-
if self.display_timestamp {
56-
if ansi {
57-
write!(writer, "{} ", Timestamp::now().dimmed())?;
58-
} else {
59-
write!(writer, "{} ", Timestamp::now())?;
60-
}
61-
}
62-
63-
if self.display_level {
64-
let level = meta.level();
65-
// Same colors as tracing
66-
if ansi {
67-
match *level {
68-
tracing::Level::TRACE => write!(writer, "{} ", level.purple())?,
69-
tracing::Level::DEBUG => write!(writer, "{} ", level.blue())?,
70-
tracing::Level::INFO => write!(writer, "{} ", level.green())?,
71-
tracing::Level::WARN => write!(writer, "{} ", level.yellow())?,
72-
tracing::Level::ERROR => write!(writer, "{} ", level.red())?,
73-
}
74-
} else {
75-
write!(writer, "{level} ")?;
76-
}
77-
}
78-
79-
if self.show_spans {
80-
let span = event.parent();
81-
let mut seen = false;
82-
83-
let span = span
84-
.and_then(|id| ctx.span(id))
85-
.or_else(|| ctx.lookup_current());
86-
87-
let scope = span.into_iter().flat_map(|span| span.scope().from_root());
88-
89-
for span in scope {
90-
seen = true;
91-
if ansi {
92-
write!(writer, "{}:", span.metadata().name().bold())?;
93-
} else {
94-
write!(writer, "{}:", span.metadata().name())?;
95-
}
96-
}
97-
98-
if seen {
99-
writer.write_char(' ')?;
100-
}
101-
}
102-
103-
ctx.field_format().format_fields(writer.by_ref(), event)?;
104-
105-
writeln!(writer)
106-
}
107-
}
108-
10928
/// Configure `tracing` based on the given [`Level`], taking into account the `RUST_LOG` environment
11029
/// variable.
11130
///
@@ -183,18 +102,11 @@ pub(crate) fn setup_logging(
183102
)
184103
.init();
185104
} else {
186-
// Regardless of the tracing level, show messages without any adornment.
187-
let format = UvFormat {
188-
display_timestamp: false,
189-
display_level: true,
190-
show_spans: false,
191-
};
192-
193105
tracing_subscriber::registry()
194106
.with(durations_layer)
195107
.with(
196108
tracing_subscriber::fmt::layer()
197-
.event_format(format)
109+
.event_format(UvFormat::default())
198110
.with_writer(writer)
199111
.with_ansi(ansi)
200112
.with_filter(filter),

0 commit comments

Comments
 (0)