1use std::collections::HashMap;2use std::fmt::Arguments;3use std::sync::{LazyLock, Mutex};45use tracing::{6 Level, Span, debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn,7 warn_span,8};9#[cfg(feature = "indicatif")]10use tracing_indicatif::span_ext::IndicatifSpanExt as _;1112#[derive(Debug)]13enum ActivityType {14 Unknown = 0,15 CopyPath = 100,16 FileTransfer = 101,17 Realise = 102,18 CopyPaths = 103,19 Builds = 104,20 Build = 105,21 OptimiseStore = 106,22 VerifyPaths = 107,23 Substitute = 108,24 QueryPathInfo = 109,25 PostBuildHook = 110,26 BuildWaiting = 111,27 FetchTree = 112,28}2930fn strip_prefix_suffix<'s, 'p>(a: &'s str, pref: &'p str, suff: &'p str) -> Option<&'s str> {31 a.strip_prefix(pref)?.strip_suffix(suff)32}3334fn parse_path(path: &str) -> &str {35 strip_prefix_suffix(path, "\x1b[35;1m", "\x1b[0m").unwrap_or(path)36}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 From<Verbosity> for tracing::Level {249 fn from(val: Verbosity) -> Self {250 match val {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}282283static NIX_SPAN_MAPPING: LazyLock<Mutex<HashMap<u64, Span>>> =284 LazyLock::new(|| Mutex::new(HashMap::new()));285286#[derive(Debug)]287enum FieldValue {288 Int(i32),289 Str(String),290}291292struct StartActivityBuilder {293 activity_id: u64,294 verbosity: Verbosity,295 typ: ActivityType,296 fields: Vec<FieldValue>,297}298impl StartActivityBuilder {299 fn add_int_field(&mut self, i: i32) {300 self.fields.push(FieldValue::Int(i));301 }302 fn add_string_field(&mut self, v: &str) {303 self.fields.push(FieldValue::Str(v.to_owned()));304 }305 fn emit(&mut self, parent: u64, s: &str) {306 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");307308 let parent = mapping.get(&parent);309310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355356 let span = {357 let _in_parent = parent.map(|p| p.enter());358 let level: Level = self.verbosity.into();359 if level == Level::ERROR {360 self.typ361 .format(&self.fields, s, |v| error_span!("action", v))362 } else if level == Level::WARN {363 self.typ364 .format(&self.fields, s, |v| warn_span!("action", v))365 } else if level == Level::INFO {366 self.typ367 .format(&self.fields, s, |v| info_span!("action", v))368 } else if level == Level::DEBUG {369 self.typ370 .format(&self.fields, s, |v| debug_span!("action", v))371 } else {372 self.typ373 .format(&self.fields, s, |v| trace_span!("action", v))374 }375 };376 if !s.trim().is_empty() {377 #[cfg(feature = "indicatif")]378 {379 span.pb_set_message(s);380 }381 let _e = span.enter();382 let level: Level = self.verbosity.into();383 if level == Level::ERROR {384 error!(target: "nix", "{}", s)385 } else if level == Level::WARN {386 warn!(target: "nix", "{}", s)387 } else if level == Level::INFO {388 info!(target: "nix", "{}", s)389 } else if level == Level::DEBUG {390 debug!(target: "nix", "{}", s)391 } else {392 trace!(target: "nix", "{}", s)393 }394 } else {395 #[cfg(feature = "indicatif")]396 {397 span.pb_start();398 }399 }400 mapping.insert(self.activity_id, span);401 }402 fn emit_result(&mut self, ty: u32) {403 let mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");404405 let Some(parent) = mapping.get(&self.activity_id) else {406 panic!("unexpected result for dead parent");407 };408409 let _in_parent = parent.enter();410 let res = ResultType::from_int(ty);411412 use FieldValue::*;413 match (&res, self.fields.as_slice()) {414 415 (ResultType::BuildLogLine, [Str(s)]) => {416 info!("{s:?}");417 }418 419 420 421 (ResultType::SetExpected, [Int(act_ty), Int(_expected)]) => {422 let _act_ty = ActivityType::from_int(*act_ty as u32);423 }424 (ResultType::SetPhase, [Str(phase)]) => {425 426 debug!(target: "nix::phase", phase)427 }428 (ResultType::Progress, [Int(_done), Int(_expected), Int(_), Int(_)]) => {429 #[cfg(feature = "indicatif")]430 {431 parent.pb_set_length(*_expected as u64);432 parent.pb_set_position(*_done as u64);433 }434 }435 _ => warn!("unknown progress report: {:?}({:?})", &res, &self.fields),436 }437 }438}439fn new_start_activity(activity_id: u64, lvl: u32, typ: u32) -> Box<StartActivityBuilder> {440 Box::new(StartActivityBuilder {441 activity_id,442 verbosity: Verbosity::from_int(lvl),443 typ: ActivityType::from_int(typ),444 fields: vec![],445 })446}447448fn emit_warn(v: &str) {449 warn!(target: "nix::eval", "{v}")450}451fn emit_stop(v: u64) {452 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");453 mapping.remove(&v);454}455fn emit_log(lvl: u32, v: &str) {456 let verbosity = Verbosity::from_int(lvl);457 let level: Level = verbosity.into();458 if level == Level::ERROR {459 error!(target: "nix", "{v}")460 } else if level == Level::WARN {461 warn!(target: "nix", "{v}")462 } else if level == Level::INFO {463 info!(target: "nix", "{v}")464 } else if level == Level::DEBUG {465 debug!(target: "nix", "{v}")466 } else {467 trace!(target: "nix", "{v}")468 }469}470471#[cxx::bridge]472pub mod nix_logging_cxx {473 extern "Rust" {474 type StartActivityBuilder;475 fn new_start_activity(activity_id: u64, lvl: u32, typ: u32) -> Box<StartActivityBuilder>;476 fn add_int_field(&mut self, i: i32);477 fn add_string_field(&mut self, v: &str);478 fn emit(&mut self, parent: u64, s: &str);479 fn emit_result(&mut self, ty: u32);480481 fn emit_warn(v: &str);482 fn emit_stop(id: u64);483 fn emit_log(lvl: u32, v: &str);484 }485 unsafe extern "C++" {486 include!("nix-eval/src/logging.hh");487488 fn apply_tracing_logger();489 }490}
1use std::collections::HashMap;2use std::fmt::Arguments;3use std::sync::{LazyLock, Mutex};45use tracing::{6 Level, Span, debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn,7 warn_span,8};9#[cfg(feature = "indicatif")]10use tracing_indicatif::span_ext::IndicatifSpanExt as _;11use vte::Parser;1213#[derive(Debug)]14enum ActivityType {15 Unknown = 0,16 CopyPath = 100,17 FileTransfer = 101,18 Realise = 102,19 CopyPaths = 103,20 Builds = 104,21 Build = 105,22 OptimiseStore = 106,23 VerifyPaths = 107,24 Substitute = 108,25 QueryPathInfo = 109,26 PostBuildHook = 110,27 BuildWaiting = 111,28 FetchTree = 112,29}3031fn strip_prefix_suffix<'s, 'p>(a: &'s str, pref: &'p str, suff: &'p str) -> Option<&'s str> {32 a.strip_prefix(pref)?.strip_suffix(suff)33}3435fn parse_path(path: &str) -> &str {36 strip_prefix_suffix(path, "\x1b[35;1m", "\x1b[0m").unwrap_or(path)37}3839fn parse_drv(drv: &str) -> &str {40 let drv = parse_path(drv);41 if let Some(pkg) = drv.strip_prefix("/nix/store/") {42 let mut it = pkg.splitn(2, '-');43 it.next();44 if let Some(pkg) = it.next() {45 return pkg;46 }47 }48 drv49}50fn parse_host(host: &str) -> &str {51 if host.is_empty() || host == "local" {52 return "local";53 }54 55 host.strip_prefix("https://").unwrap_or(host)56}5758impl ActivityType {59 fn name(&self) -> &'static str {60 match self {61 ActivityType::Unknown => "nix",62 ActivityType::CopyPath => "nix::copy-path",63 ActivityType::FileTransfer => "nix::file-transfer",64 ActivityType::Realise => "nix::realise",65 ActivityType::CopyPaths => "nix::copy-paths",66 ActivityType::Builds => "nix::builds",67 ActivityType::Build => "nix::build",68 ActivityType::OptimiseStore => "nix::optimise-store",69 ActivityType::VerifyPaths => "nix::verify-paths",70 ActivityType::Substitute => "nix::substitute",71 ActivityType::QueryPathInfo => "nix::query-path-info",72 ActivityType::PostBuildHook => "nix::post-build-hook",73 ActivityType::BuildWaiting => "nix::build-waiting",74 ActivityType::FetchTree => "nix::fetch-tree",75 }76 }77 fn format(78 &self,79 values: &[FieldValue],80 s: &str,81 into: impl FnOnce(Arguments<'_>) -> Span,82 ) -> Span {83 use FieldValue::*;84 match (self, values) {85 (ActivityType::QueryPathInfo, [Str(drv), Str(host)]) => {86 let drv = parse_drv(drv);87 let host = parse_host(host);88 debug_span!(target: "nix::query-path-info", "querying", drv, host)89 }90 (ActivityType::Substitute, [Str(drv), Str(host)]) => {91 let drv = parse_drv(drv);92 let host = parse_host(host);93 debug_span!(target: "nix::substitute", "substituting", drv, host)94 }95 (ActivityType::CopyPath, [Str(drv), Str(from), Str(to)]) => {96 let drv = parse_drv(drv);97 let from = parse_host(from);98 let to = parse_host(to);99 debug_span!(target: "nix::copy-path", "copying", drv, from, to)100 }101 (ActivityType::Build, [Str(drv), Str(host), Int(_), Int(_)]) => {102 let drv = parse_drv(drv);103 let host = parse_host(host);104 info_span!(target: "nix::build", "building", drv, host)105 }106 (ActivityType::FileTransfer, [Str(file)]) => {107 info_span!(target: "nix::file-transfer", "downloading", file)108 }109 (ActivityType::Realise, []) => {110 debug_span!(target: "nix::realise", "realising")111 }112 (ActivityType::CopyPaths, []) => {113 debug_span!(target: "nix::copy-paths", "copying paths")114 }115 (ActivityType::Unknown, [])116 if s.starts_with("copying \"") && s.ends_with("\" to the store") =>117 {118 let tree = s119 .trim_start_matches("copying \"")120 .trim_end_matches("\" to the store");121 debug_span!(target: "nix::trees", "copying", tree)122 }123 (ActivityType::Unknown, [])124 if s.starts_with("copying '") && s.ends_with("' to the store") =>125 {126 let tree = s127 .trim_start_matches("copying '")128 .trim_end_matches("' to the store");129 debug_span!(target: "nix::trees", "copying", tree)130 }131 (ActivityType::Unknown, []) if s.starts_with("hashing '") && s.ends_with("'") => {132 let tree = s.trim_start_matches("hashing '").trim_end_matches("'");133 debug_span!(target: "nix::trees", "hashing", tree)134 }135 (ActivityType::Unknown, []) if s.starts_with("connecting to '") && s.ends_with("'") => {136 let host = s137 .trim_start_matches("connecting to '")138 .trim_end_matches("'");139 debug_span!(target: "nix::remote", "connecting", host)140 }141 (ActivityType::Unknown, [])142 if s.starts_with("copying outputs from '") && s.ends_with("'") =>143 {144 let host = s145 .trim_start_matches("copying outputs from '")146 .trim_end_matches("'");147 debug_span!(target: "nix::remote", "copying outputs", host)148 }149 (ActivityType::Unknown, [])150 if s.starts_with("copying dependencies to '") && s.ends_with("'") =>151 {152 let host = s153 .trim_start_matches("copying dependencies to '")154 .trim_end_matches("'");155 debug_span!(target: "nix::remote", "copying dependencies", host)156 }157 (ActivityType::Unknown, [])158 if s.starts_with("waiting for the upload lock to '") && s.ends_with("'") =>159 {160 let host = s161 .trim_start_matches("waiting for the upload lock to '")162 .trim_end_matches("'");163 debug_span!(target: "nix::remote", "waiting for upload lock", host)164 }165 (ActivityType::BuildWaiting, [])166 if s.starts_with("waiting for a machine to build '") && s.ends_with("'") =>167 {168 let drv = parse_drv(169 s.trim_start_matches("waiting for a machine to build '")170 .trim_end_matches("'"),171 );172 debug_span!(target: "nix::build-waiting", "waiting for available builder", drv)173 }174 (ActivityType::Unknown, []) if s == "querying info about missing paths" => {175 debug_span!(target: "nix::remote", "querying")176 }177 _ => into(format_args!("{}({values:?})", self.name())),178 }179 }180 fn from_int(v: u32) -> Self {181 match v {182 0 => Self::Unknown,183 100 => Self::CopyPath,184 101 => Self::FileTransfer,185 102 => Self::Realise,186 103 => Self::CopyPaths,187 104 => Self::Builds,188 105 => Self::Build,189 106 => Self::OptimiseStore,190 107 => Self::VerifyPaths,191 108 => Self::Substitute,192 109 => Self::QueryPathInfo,193 110 => Self::PostBuildHook,194 111 => Self::BuildWaiting,195 112 => Self::FetchTree,196 _ => {197 warn!("unknown nix action: {v}");198 Self::Unknown199 }200 }201 }202}203204#[derive(Debug)]205enum ResultType {206 FileLinked = 100,207 BuildLogLine = 101,208 UntrustedPath = 102,209 CorruptedPath = 103,210 SetPhase = 104,211 Progress = 105,212 SetExpected = 106,213 PostBuildLogLine = 107,214 FetchStatus = 108,215216 Unknown = 999,217}218impl ResultType {219 fn from_int(v: u32) -> Self {220 match v {221 100 => Self::FileLinked,222 101 => Self::BuildLogLine,223 102 => Self::UntrustedPath,224 103 => Self::CorruptedPath,225 104 => Self::SetPhase,226 105 => Self::Progress,227 106 => Self::SetExpected,228 107 => Self::PostBuildLogLine,229 108 => Self::FetchStatus,230231 _ => {232 warn!("unknown nix result: {v}");233 Self::Unknown234 }235 }236 }237}238#[derive(Clone, Copy)]239enum Verbosity {240 Error,241 Warn,242 Notice,243 Info,244 Talkative,245 Chatty,246 Debug,247 Vomit,248}249impl From<Verbosity> for tracing::Level {250 fn from(val: Verbosity) -> Self {251 match val {252 Verbosity::Error => Level::ERROR,253 Verbosity::Warn => Level::WARN,254 Verbosity::Notice => Level::WARN,255 Verbosity::Info => Level::INFO,256 Verbosity::Talkative => Level::DEBUG,257 Verbosity::Chatty => Level::DEBUG,258 Verbosity::Debug => Level::DEBUG,259 Verbosity::Vomit => Level::TRACE,260 }261 }262}263impl Verbosity {264 fn from_int(u: u32) -> Self {265 [266 Self::Error,267 Self::Warn,268 Self::Notice,269 Self::Info,270 Self::Talkative,271 Self::Chatty,272 Self::Debug,273 Self::Vomit,274 ]275 .get(u as usize)276 .cloned()277 .unwrap_or_else(|| {278 warn!("unknown log level: {u}");279 Verbosity::Vomit280 })281 }282}283284static NIX_SPAN_MAPPING: LazyLock<Mutex<HashMap<u64, Span>>> =285 LazyLock::new(|| Mutex::new(HashMap::new()));286287#[derive(Debug)]288enum FieldValue {289 Int(i32),290 Str(String),291}292293struct StartActivityBuilder {294 activity_id: u64,295 verbosity: Verbosity,296 typ: ActivityType,297 fields: Vec<FieldValue>,298}299impl StartActivityBuilder {300 fn add_int_field(&mut self, i: i32) {301 self.fields.push(FieldValue::Int(i));302 }303 fn add_string_field(&mut self, v: &str) {304 self.fields.push(FieldValue::Str(v.to_owned()));305 }306 fn emit(&mut self, parent: u64, s: &str) {307 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");308309 let parent = mapping.get(&parent);310311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356357 let span = {358 let _in_parent = parent.map(|p| p.enter());359 let level: Level = self.verbosity.into();360 if level == Level::ERROR {361 self.typ362 .format(&self.fields, s, |v| error_span!("action", v))363 } else if level == Level::WARN {364 self.typ365 .format(&self.fields, s, |v| warn_span!("action", v))366 } else if level == Level::INFO {367 self.typ368 .format(&self.fields, s, |v| info_span!("action", v))369 } else if level == Level::DEBUG {370 self.typ371 .format(&self.fields, s, |v| debug_span!("action", v))372 } else {373 self.typ374 .format(&self.fields, s, |v| trace_span!("action", v))375 }376 };377 if !s.trim().is_empty() {378 let s = ansi_filter(s);379 #[cfg(feature = "indicatif")]380 {381 span.pb_set_message(s);382 }383 let _e = span.enter();384 let level: Level = self.verbosity.into();385 if level == Level::ERROR {386 error!(target: "nix", "{}", s)387 } else if level == Level::WARN {388 warn!(target: "nix", "{}", s)389 } else if level == Level::INFO {390 info!(target: "nix", "{}", s)391 } else if level == Level::DEBUG {392 debug!(target: "nix", "{}", s)393 } else {394 trace!(target: "nix", "{}", s)395 }396 } else {397 #[cfg(feature = "indicatif")]398 {399 span.pb_start();400 }401 }402 mapping.insert(self.activity_id, span);403 }404 fn emit_result(&mut self, ty: u32) {405 let mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");406407 let Some(parent) = mapping.get(&self.activity_id) else {408 panic!("unexpected result for dead parent");409 };410411 let _in_parent = parent.enter();412 let res = ResultType::from_int(ty);413414 use FieldValue::*;415 match (&res, self.fields.as_slice()) {416 417 (ResultType::BuildLogLine, [Str(s)]) => {418 let s = ansi_filter(s);419 info!("{s}");420 }421 422 423 424 (ResultType::SetExpected, [Int(act_ty), Int(_expected)]) => {425 let _act_ty = ActivityType::from_int(*act_ty as u32);426 }427 (ResultType::SetPhase, [Str(phase)]) => {428 429 debug!(target: "nix::phase", phase)430 }431 (ResultType::Progress, [Int(_done), Int(_expected), Int(_), Int(_)]) => {432 #[cfg(feature = "indicatif")]433 {434 parent.pb_set_length(*_expected as u64);435 parent.pb_set_position(*_done as u64);436 }437 }438 _ => warn!("unknown progress report: {:?}({:?})", &res, &self.fields),439 }440 }441}442fn new_start_activity(activity_id: u64, lvl: u32, typ: u32) -> Box<StartActivityBuilder> {443 Box::new(StartActivityBuilder {444 activity_id,445 verbosity: Verbosity::from_int(lvl),446 typ: ActivityType::from_int(typ),447 fields: vec![],448 })449}450451fn emit_warn(v: &str) {452 warn!(target: "nix::eval", "{v}")453}454fn emit_stop(v: u64) {455 let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");456 mapping.remove(&v);457}458fn emit_log(lvl: u32, v: &str) {459 let verbosity = Verbosity::from_int(lvl);460 let level: Level = verbosity.into();461 if level == Level::ERROR {462 error!(target: "nix", "{v}")463 } else if level == Level::WARN {464 warn!(target: "nix", "{v}")465 } else if level == Level::INFO {466 info!(target: "nix", "{v}")467 } else if level == Level::DEBUG {468 debug!(target: "nix", "{v}")469 } else {470 trace!(target: "nix", "{v}")471 }472}473474struct AnsiFiltered {475 output: String,476}477impl vte::Perform for AnsiFiltered {478 fn print(&mut self, c: char) {479 self.output.push(c);480 }481482 fn execute(&mut self, byte: u8) {483 484 if byte == b'\n' {485 self.output.push('\n');486 } else if byte == b'\t' {487 488 self.output.push('\t');489 }490 }491492 fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {}493 fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {}494495 fn csi_dispatch(496 &mut self,497 params: &vte::Params,498 _intermediates: &[u8],499 _ignore: bool,500 action: char,501 ) {502 use std::fmt::Write;503 if action != 'm' {504 505 return;506 }507 self.output.push_str("IDK\x1b[");508 for (i, par) in params.iter().enumerate() {509 if i != 0 {510 let _ = write!(self.output, ";");511 }512 for (i, sub) in par.iter().enumerate() {513 if i != 0 {514 let _ = write!(self.output, ":");515 }516 let _ = write!(self.output, "{sub}");517 }518 }519 self.output.push(action);520 }521}522fn ansi_filter(i: &str) -> String {523 let mut out = AnsiFiltered {524 output: String::new(),525 };526 let mut parser = Parser::new();527528 529 for chunk in i.as_bytes().chunks(50) {530 parser.advance(&mut out, chunk);531 }532533 out.output534}535536#[cxx::bridge]537pub mod nix_logging_cxx {538 extern "Rust" {539 type StartActivityBuilder;540 fn new_start_activity(activity_id: u64, lvl: u32, typ: u32) -> Box<StartActivityBuilder>;541 fn add_int_field(&mut self, i: i32);542 fn add_string_field(&mut self, v: &str);543 fn emit(&mut self, parent: u64, s: &str);544 fn emit_result(&mut self, ty: u32);545546 fn emit_warn(v: &str);547 fn emit_stop(id: u64);548 fn emit_log(lvl: u32, v: &str);549 }550 unsafe extern "C++" {551 include!("nix-eval/src/logging.hh");552553 fn apply_tracing_logger();554 }555}