1use std::collections::HashMap;2use std::fmt::Arguments;3use std::sync::{LazyLock, Mutex};45use tracing::{6 Level, Metadata, Span, debug, debug_span, error, error_span, event, info, info_span, trace,7 trace_span, warn, warn_span,8};9use tracing_indicatif::span_ext::IndicatifSpanExt as _;1011#[derive(Debug)]12enum ActivityType {13 Unknown = 0,14 CopyPath = 100,15 FileTransfer = 101,16 Realise = 102,17 CopyPaths = 103,18 Builds = 104,19 Build = 105,20 OptimiseStore = 106,21 VerifyPaths = 107,22 Substitute = 108,23 QueryPathInfo = 109,24 PostBuildHook = 110,25 BuildWaiting = 111,26 FetchTree = 112,27}2829fn strip_prefix_suffix<'s, 'p>(a: &'s str, pref: &'p str, suff: &'p str) -> Option<&'s str> {30 a.strip_prefix(pref)?.strip_suffix(suff)31}3233fn parse_path(path: &str) -> &str {34 let path = strip_prefix_suffix(path, "\x1b[35;1m", "\x1b[0m").unwrap_or(path);35 path36}3738fn parse_drv(drv: &str) -> &str {39 let drv = parse_path(drv);40 if let Some(pkg) = drv.strip_prefix("/nix/store/") {41 let mut it = pkg.splitn(2, '-');42 it.next();43 if let Some(pkg) = it.next() {44 return pkg;45 }46 }47 drv48}49fn parse_host(host: &str) -> &str {50 if host.is_empty() || host == "local" {51 return "local";52 }53 54 host.strip_prefix("https://").unwrap_or(host)55}5657impl ActivityType {58 fn name(&self) -> &'static str {59 match self {60 ActivityType::Unknown => "nix",61 ActivityType::CopyPath => "nix::copy-path",62 ActivityType::FileTransfer => "nix::file-transfer",63 ActivityType::Realise => "nix::realise",64 ActivityType::CopyPaths => "nix::copy-paths",65 ActivityType::Builds => "nix::builds",66 ActivityType::Build => "nix::build",67 ActivityType::OptimiseStore => "nix::optimise-store",68 ActivityType::VerifyPaths => "nix::verify-paths",69 ActivityType::Substitute => "nix::substitute",70 ActivityType::QueryPathInfo => "nix::query-path-info",71 ActivityType::PostBuildHook => "nix::post-build-hook",72 ActivityType::BuildWaiting => "nix::build-waiting",73 ActivityType::FetchTree => "nix::fetch-tree",74 }75 }76 fn format(77 &self,78 values: &[FieldValue],79 s: &str,80 into: impl FnOnce(Arguments<'_>) -> Span,81 ) -> Span {82 use FieldValue::*;83 match (self, values) {84 (ActivityType::QueryPathInfo, [Str(drv), Str(host)]) => {85 let drv = parse_drv(drv);86 let host = parse_host(host);87 debug_span!(target: "nix::query-path-info", "querying", drv, host)88 }89 (ActivityType::Substitute, [Str(drv), Str(host)]) => {90 let drv = parse_drv(drv);91 let host = parse_host(host);92 debug_span!(target: "nix::substitute", "substituting", drv, host)93 }94 (ActivityType::CopyPath, [Str(drv), Str(from), Str(to)]) => {95 let drv = parse_drv(drv);96 let from = parse_host(from);97 let to = parse_host(to);98 debug_span!(target: "nix::copy-path", "copying", drv, from, to)99 }100 (ActivityType::Build, [Str(drv), Str(host), Int(_), Int(_)]) => {101 let drv = parse_drv(drv);102 let host = parse_host(host);103 info_span!(target: "nix::build", "building", drv, host)104 }105 (ActivityType::FileTransfer, [Str(file)]) => {106 info_span!(target: "nix::file-transfer", "downloading", file)107 }108 (ActivityType::Realise, []) => {109 debug_span!(target: "nix::realise", "realising")110 }111 (ActivityType::CopyPaths, []) => {112 debug_span!(target: "nix::copy-paths", "copying paths")113 }114 (ActivityType::Unknown, [])115 if s.starts_with("copying \"") && s.ends_with("\" to the store") =>116 {117 let tree = s118 .trim_start_matches("copying \"")119 .trim_end_matches("\" to the store");120 debug_span!(target: "nix::trees", "copying", tree)121 }122 (ActivityType::Unknown, [])123 if s.starts_with("copying '") && s.ends_with("' to the store") =>124 {125 let tree = s126 .trim_start_matches("copying '")127 .trim_end_matches("' to the store");128 debug_span!(target: "nix::trees", "copying", tree)129 }130 (ActivityType::Unknown, []) if s.starts_with("hashing '") && s.ends_with("'") => {131 let tree = s.trim_start_matches("hashing '").trim_end_matches("'");132 debug_span!(target: "nix::trees", "hashing", tree)133 }134 (ActivityType::Unknown, []) if s.starts_with("connecting to '") && s.ends_with("'") => {135 let host = s136 .trim_start_matches("connecting to '")137 .trim_end_matches("'");138 debug_span!(target: "nix::remote", "connecting", host)139 }140 (ActivityType::Unknown, [])141 if s.starts_with("copying outputs from '") && s.ends_with("'") =>142 {143 let host = s144 .trim_start_matches("copying outputs from '")145 .trim_end_matches("'");146 debug_span!(target: "nix::remote", "copying outputs", host)147 }148 (ActivityType::Unknown, [])149 if s.starts_with("copying dependencies to '") && s.ends_with("'") =>150 {151 let host = s152 .trim_start_matches("copying dependencies to '")153 .trim_end_matches("'");154 debug_span!(target: "nix::remote", "copying dependencies", host)155 }156 (ActivityType::Unknown, [])157 if s.starts_with("waiting for the upload lock to '") && s.ends_with("'") =>158 {159 let host = s160 .trim_start_matches("waiting for the upload lock to '")161 .trim_end_matches("'");162 debug_span!(target: "nix::remote", "waiting for upload lock", host)163 }164 (ActivityType::BuildWaiting, [])165 if s.starts_with("waiting for a machine to build '") && s.ends_with("'") =>166 {167 let drv = parse_drv(168 s.trim_start_matches("waiting for a machine to build '")169 .trim_end_matches("'"),170 );171 debug_span!(target: "nix::build-waiting", "waiting for available builder", drv)172 }173 (ActivityType::Unknown, []) if s == "querying info about missing paths" => {174 debug_span!(target: "nix::remote", "querying")175 }176 _ => into(format_args!("{}({values:?})", self.name())),177 }178 }179 fn from_int(v: u32) -> Self {180 match v {181 0 => Self::Unknown,182 100 => Self::CopyPath,183 101 => Self::FileTransfer,184 102 => Self::Realise,185 103 => Self::CopyPaths,186 104 => Self::Builds,187 105 => Self::Build,188 106 => Self::OptimiseStore,189 107 => Self::VerifyPaths,190 108 => Self::Substitute,191 109 => Self::QueryPathInfo,192 110 => Self::PostBuildHook,193 111 => Self::BuildWaiting,194 112 => Self::FetchTree,195 _ => {196 warn!("unknown nix action: {v}");197 Self::Unknown198 }199 }200 }201}202203#[derive(Debug)]204enum ResultType {205 FileLinked = 100,206 BuildLogLine = 101,207 UntrustedPath = 102,208 CorruptedPath = 103,209 SetPhase = 104,210 Progress = 105,211 SetExpected = 106,212 PostBuildLogLine = 107,213 FetchStatus = 108,214215 Unknown = 999,216}217impl ResultType {218 fn from_int(v: u32) -> Self {219 match v {220 100 => Self::FileLinked,221 101 => Self::BuildLogLine,222 102 => Self::UntrustedPath,223 103 => Self::CorruptedPath,224 104 => Self::SetPhase,225 105 => Self::Progress,226 106 => Self::SetExpected,227 107 => Self::PostBuildLogLine,228 108 => Self::FetchStatus,229230 _ => {231 warn!("unknown nix result: {v}");232 Self::Unknown233 }234 }235 }236}237#[derive(Clone, Copy)]238enum Verbosity {239 Error,240 Warn,241 Notice,242 Info,243 Talkative,244 Chatty,245 Debug,246 Vomit,247}248impl Into<tracing::Level> for Verbosity {249 fn into(self) -> tracing::Level {250 match self {251 Verbosity::Error => Level::ERROR,252 Verbosity::Warn => Level::WARN,253 Verbosity::Notice => Level::WARN,254 Verbosity::Info => Level::INFO,255 Verbosity::Talkative => Level::DEBUG,256 Verbosity::Chatty => Level::DEBUG,257 Verbosity::Debug => Level::DEBUG,258 Verbosity::Vomit => Level::TRACE,259 }260 }261}262impl Verbosity {263 fn from_int(u: u32) -> Self {264 [265 Self::Error,266 Self::Warn,267 Self::Notice,268 Self::Info,269 Self::Talkative,270 Self::Chatty,271 Self::Debug,272 Self::Vomit,273 ]274 .get(u as usize)275 .cloned()276 .unwrap_or_else(|| {277 warn!("unknown log level: {u}");278 Verbosity::Vomit279 })280 }281}282283#[derive(Hash, PartialEq, Eq, Clone, Copy)]284enum MetadataKind {285 Span,286 Event,287}288289290291292293294295296297#[derive(Hash, PartialEq, Eq)]298struct ForeignMetadataInfo {299 target: &'static str,300 level: Level,301 kind: MetadataKind,302 name: &'static str,303 module: Option<&'static str>,304 file: Option<&'static str>,305 line: Option<u32>,306 names: &'static [&'static str],307}308309struct FakeCallsite;310impl tracing::callsite::Callsite for FakeCallsite {311 fn set_interest(&self, interest: tracing::subscriber::Interest) {312 unreachable!()313 }314315 fn metadata(&self) -> &Metadata<'_> {316 unreachable!()317 }318}319const FAKE_CALLSITE: FakeCallsite = FakeCallsite;320321#[cfg(false)]322#[derive(Default)]323struct ForeignSpanData {324 interned: HashSet<&'static str>,325 metadatas: HashMap<ForeignMetadataInfo, &'static Metadata<'static>>,326}327#[cfg(false)]328impl ForeignSpanData {329 fn intern(&mut self, s: &str) -> &'static str {330 if let Some(v) = self.interned.get(s) {331 return *v;332 }333 let leaked: Box<str> = s.into();334 let leaked = Box::leak(leaked);335 self.interned.insert(leaked);336 return leaked;337 }338 fn alloc_metadata<'t>(339 &'t mut self,340 target: &'static str,341 level: Level,342 kind: MetadataKind,343 name: &'static str,344 module: Option<&'static str>,345 file: Option<&'static str>,346 line: Option<u32>,347 names: &'static [&'static str],348 ) -> &'static Metadata<'static> {349 let info = ForeignMetadataInfo {350 target,351 level,352 kind,353 name,354 module,355 file,356 line,357 names,358 };359 if let Some(v) = self.metadatas.get(&info) {360 return *v;361 }362 let fake = FakeCallsite;363 let metadata = Box::leak::<'static>(Box::new(Metadata::new(364 name,365 target,366 level,367 file,368 line,369 module,370 FieldSet::new(names, tracing::callsite::Identifier(&FAKE_CALLSITE)),371 kind.kind(),372 )));373374 let meta_raw = &raw const *metadata;375 let fields_raw = &raw const *metadata.fields();376377 378 379 let fields_offset = unsafe { fields_raw.cast::<u8>().offset_from(meta_raw.cast()) };380 let field_set = unsafe {381 ((&raw mut *metadata).cast::<()>())382 .byte_offset(fields_offset)383 .cast::<FieldSet>()384 };385 386 387 let callsite = Box::leak(Box::new(tracing::callsite::DefaultCallsite::new(metadata)));388 unsafe { *field_set = FieldSet::new(names, tracing::callsite::Identifier(callsite)) };389390 tracing::callsite::register(&*callsite);391392 self.metadatas.insert(info, metadata);393 return metadata;394 }395}396397#[cfg(false)]398static FOREIGN_SPAN_DATA: LazyLock<Mutex<ForeignSpanData>> =399 LazyLock::new(|| Mutex::new(ForeignSpanData::default()));400static NIX_SPAN_MAPPING: LazyLock<Mutex<HashMap<u64, Span>>> =401 LazyLock::new(|| Mutex::new(HashMap::new()));402403#[derive(Debug)]404enum FieldValue {405 Int(i32),406 Str(String),407}408409struct StartActivityBuilder {410 activity_id: u64,411 verbosity: Verbosity,412 typ: ActivityType,413 fields: Vec<FieldValue>,414}415impl StartActivityBuilder {416 fn add_int_field(&mut self, i: i32) {417 self.fields.push(FieldValue::Int(i));418 }419 fn add_string_field(&mut self, v: &str) {420 self.fields.push(FieldValue::Str(v.to_owned()));421 }422 fn emit(&mut self, parent: u64, s: &str) {423 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");424425 let parent = mapping.get(&parent);426427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472473 let span = {474 let _in_parent = parent.map(|p| p.enter());475 let level: Level = self.verbosity.into();476 if level == Level::ERROR {477 self.typ478 .format(&self.fields, s, |v| error_span!("action", v))479 } else if level == Level::WARN {480 self.typ481 .format(&self.fields, s, |v| warn_span!("action", v))482 } else if level == Level::INFO {483 self.typ484 .format(&self.fields, s, |v| info_span!("action", v))485 } else if level == Level::DEBUG {486 self.typ487 .format(&self.fields, s, |v| debug_span!("action", v))488 } else {489 self.typ490 .format(&self.fields, s, |v| trace_span!("action", v))491 }492 };493 if !s.trim().is_empty() {494 span.pb_set_message(s);495 let _e = span.enter();496 let level: Level = self.verbosity.into();497 if level == Level::ERROR {498 error!(target: "nix", "{}", s)499 } else if level == Level::WARN {500 warn!(target: "nix", "{}", s)501 } else if level == Level::INFO {502 info!(target: "nix", "{}", s)503 } else if level == Level::DEBUG {504 debug!(target: "nix", "{}", s)505 } else {506 trace!(target: "nix", "{}", s)507 }508 } else {509 span.pb_start();510 }511 mapping.insert(self.activity_id, span);512 }513 fn emit_result(&mut self, ty: u32) {514 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");515516 let Some(parent) = mapping.get(&self.activity_id) else {517 panic!("unexpected result for dead parent");518 };519520 let _in_parent = parent.enter();521 let res = ResultType::from_int(ty);522523 use FieldValue::*;524 match (&res, self.fields.as_slice()) {525 526 (ResultType::BuildLogLine, [Str(s)]) => {527 info!("{s:?}");528 }529 530 531 532 (ResultType::SetExpected, [Int(act_ty), Int(_expected)]) => {533 let _act_ty = ActivityType::from_int(*act_ty as u32);534 }535 (ResultType::SetPhase, [Str(phase)]) => {536 537 debug!(target: "nix::phase", phase)538 }539 (ResultType::Progress, [Int(done), Int(expected), Int(_), Int(_)]) => {540 parent.pb_set_length(*expected as u64);541 parent.pb_set_position(*done as u64);542 }543 _ => warn!("unknown progress report: {:?}({:?})", &res, &self.fields),544 }545 }546}547fn new_start_activity(activity_id: u64, lvl: u32, typ: u32) -> Box<StartActivityBuilder> {548 Box::new(StartActivityBuilder {549 activity_id,550 verbosity: Verbosity::from_int(lvl),551 typ: ActivityType::from_int(typ),552 fields: vec![],553 })554}555556fn emit_warn(v: &str) {557 warn!(target: "nix::eval", "{v}")558}559fn emit_stop(v: u64) {560 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");561 mapping.remove(&v);562}563fn emit_log(lvl: u32, v: &str) {564 let verbosity = Verbosity::from_int(lvl);565 let level: Level = verbosity.into();566 if level == Level::ERROR {567 error!(target: "nix", "{v}")568 } else if level == Level::WARN {569 warn!(target: "nix", "{v}")570 } else if level == Level::INFO {571 info!(target: "nix", "{v}")572 } else if level == Level::DEBUG {573 debug!(target: "nix", "{v}")574 } else {575 trace!(target: "nix", "{v}")576 }577}578579580581582583#[cxx::bridge]584pub mod nix_logging_cxx {585 extern "Rust" {586 type StartActivityBuilder;587 fn new_start_activity(activity_id: u64, lvl: u32, typ: u32) -> Box<StartActivityBuilder>;588 fn add_int_field(&mut self, i: i32);589 fn add_string_field(&mut self, v: &str);590 fn emit(&mut self, parent: u64, s: &str);591 fn emit_result(&mut self, ty: u32);592593 fn emit_warn(v: &str);594 fn emit_stop(id: u64);595 fn emit_log(lvl: u32, v: &str);596 }597 unsafe extern "C++" {598 include!("nix-eval/src/logging.hh");599600 fn apply_tracing_logger();601 }602}