1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Bounded metadata for replaying configuration discovery diagnostics.
//!
//! Discovery captures this data while it still owns the selected paths and
//! project-scope result. Startup later replays the original trace events after
//! it enables verbose output, without accessing the environment or filesystem.
use tracing::debug;
use super::ConfigPathResolution;
use super::diagnostics::{
BoundedConfigPath, ConfigLoadWarning, debug_config_path_from_fields,
trace_config_path_variable_from_fields,
};
use super::layers::ProjectScopeTrace;
/// Bounded selector and layer-branch diagnostics retained after discovery.
#[derive(Clone, Debug)]
pub(super) struct DiscoveryTrace {
resolution: ConfigPathTrace,
file_layers: FileLayerTrace,
}
impl DiscoveryTrace {
/// Combine a resolved selector and layer branch without retaining raw paths.
pub(super) fn new(resolution: &ConfigPathResolution, file_layers: FileLayerTrace) -> Self {
Self {
resolution: ConfigPathTrace::from_resolution(resolution),
file_layers,
}
}
/// Emit all discovery diagnostics from bounded metadata only.
pub(super) fn emit(&self) {
self.resolution.emit();
self.file_layers.emit();
}
}
/// Bounded diagnostics for selector resolution and its environment reads.
#[derive(Clone, Debug)]
struct ConfigPathTrace {
selector: &'static str,
path: BoundedConfigPath,
environment_lookups: Vec<(&'static str, BoundedConfigPath)>,
}
impl ConfigPathTrace {
/// Build a trace-only view without retaining raw paths.
fn from_resolution(resolution: &ConfigPathResolution) -> Self {
Self {
selector: resolution.selector,
path: BoundedConfigPath::from_path(resolution.path.as_deref()),
environment_lookups: resolution
.environment_lookups
.iter()
.map(|(var_name, path)| (*var_name, BoundedConfigPath::from_path(path.as_deref())))
.collect(),
}
}
/// Emit selector diagnostics from their bounded representation.
fn emit(&self) {
for (var_name, path) in &self.environment_lookups {
trace_config_path_variable_from_fields(var_name, path);
}
debug!(
selector = self.selector,
path_hash = self.path.hash.as_deref(),
path_present = self.path.is_present,
"resolved config path"
);
}
}
/// File-layer branch diagnostics retained after the first discovery pass.
#[derive(Clone, Debug)]
pub(super) enum FileLayerTrace {
/// An explicit CLI or environment selector chose a configuration path.
Explicit { path: BoundedConfigPath },
/// Selector-free discovery, with any project-scope second-pass outcome.
Automatic {
project_scope: Option<ProjectScopeTrace>,
},
}
impl FileLayerTrace {
/// Emit the selected layer-collection branch without filesystem access.
fn emit(&self) {
match self {
Self::Explicit { path } => {
debug_config_path_from_fields("using explicit config path", path);
}
Self::Automatic { project_scope } => {
debug!("using config discovery");
if let Some(trace) = project_scope {
trace.emit();
}
}
}
}
}
/// Diagnostics deferred until a composition boundary enables its filter.
#[derive(Clone, Debug)]
pub(super) struct DiscoveryDiagnostics {
trace: DiscoveryTrace,
load_warning: Option<ConfigLoadWarning>,
}
impl DiscoveryDiagnostics {
/// Combine bounded discovery events and any explicit-load warning.
pub(super) const fn new(
trace: DiscoveryTrace,
load_warning: Option<ConfigLoadWarning>,
) -> Self {
Self {
trace,
load_warning,
}
}
/// Emit deferred diagnostics without querying the environment or filesystem.
pub(super) fn emit(&self) {
self.trace.emit();
if let Some(warning) = &self.load_warning {
warning.emit();
}
}
}