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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//! Configuration-load orchestration and its elapsed-time seam.
//!
//! This module keeps the measured startup interval bounded by diagnostic-mode
//! resolution and configuration merging while allowing tests to supply a
//! deterministic elapsed time.
use clap::ArgMatches;
use monotony::MonotonicClock;
use netsuke::cli;
use std::{
io::{self, Write},
process::ExitCode,
time::Duration,
};
use super::{
DiagMode, StartupWriter, diagnostic_json, observability, set_tracing_filter,
settle_startup_diagnostics, startup_filter,
};
/// Cached diagnostic resolution carried into the full configuration merge.
struct ResolvedDiagnosticMode {
/// The diagnostic mode resolved during startup.
mode: DiagMode,
/// File layers preserved from the shared discovery pass.
discovered_layers: cli::DiscoveredLayers,
}
/// Dependencies that define one configuration-load attempt.
///
/// This context is private to startup orchestration; it groups the parsed
/// inputs that must remain within the measured configuration-load interval.
pub(super) struct ConfigurationLoadContext<'a, E>
where
E: cli::ConfigEnvProvider,
{
/// CLI values parsed before configuration was discovered.
pub(super) parsed_cli: &'a cli::Cli,
/// Clap argument matches backing the parsed CLI values.
pub(super) matches: &'a ArgMatches,
/// Diagnostic mode used when startup configuration resolution fails.
pub(super) startup_mode: DiagMode,
/// Writes startup diagnostics before tracing is configured.
pub(super) startup_writer: &'a StartupWriter,
/// Environment provider consulted during configuration discovery and merge.
pub(super) config_env: &'a E,
}
/// Resolve diagnostic mode and merge configuration while recording one metric.
///
/// The measured interval starts immediately before diagnostic-mode resolution
/// and ends after either that resolution or the full configuration merge.
///
/// # Errors
///
/// Returns a failure [`ExitCode`] when diagnostic-mode resolution or the
/// subsequent configuration merge fails.
pub(super) fn resolve_configuration<E>(
context: &ConfigurationLoadContext<'_, E>,
clock: &impl MonotonicClock,
) -> Result<cli::Cli, std::process::ExitCode>
where
E: cli::ConfigEnvProvider,
{
let started_at = clock.now();
let resolution = match resolve_json_mode_or_exit(context, clock) {
Ok(mode) => mode,
Err(code) => {
record_config_load_metrics(clock.now().duration_since(started_at), false);
settle_startup_diagnostics(context.startup_writer, context.startup_mode);
return Err(code);
}
};
// The effective mode is known here, before configuration is merged, so the
// startup warning reaches the user ahead of any configuration processing.
settle_startup_diagnostics(context.startup_writer, resolution.mode);
let merged_cli = match merge_cli_or_exit(context, resolution, clock) {
Ok(merged) => merged,
Err(code) => {
record_config_load_metrics(clock.now().duration_since(started_at), false);
return Err(code);
}
};
record_config_load_metrics(clock.now().duration_since(started_at), true);
Ok(merged_cli)
}
/// Report a configuration error and return its failure [`ExitCode`].
///
/// JSON mode emits a valid, stable diagnostic document with its serialization
/// fallback. Human mode logs only bounded `operation` and `error_category`
/// fields structurally before writing the user-facing error to stderr.
pub(super) fn config_err_to_exit(
err: &(dyn std::error::Error + 'static),
mode: DiagMode,
operation: &'static str,
) -> ExitCode {
if mode.is_json() {
diagnostic_json::emit_or_fallback(diagnostic_json::render_error_json(err))
} else {
tracing::error!(
operation,
error_category = observability::classify_error(err),
"configuration load failed"
);
drop(writeln!(io::stderr(), "{err}"));
ExitCode::FAILURE
}
}
/// Resolve JSON mode and cache discovered configuration layers for the merge.
///
/// The diagnostic-mode phase is timed with the injected [`MonotonicClock`].
/// Resolution failures select the fallback mode's filter and return a failure
/// [`ExitCode`] through [`config_err_to_exit`].
///
/// # Errors
///
/// Returns a failure [`ExitCode`] when config discovery or JSON preference
/// resolution fails.
fn resolve_json_mode_or_exit<E>(
context: &ConfigurationLoadContext<'_, E>,
clock: &impl MonotonicClock,
) -> Result<ResolvedDiagnosticMode, ExitCode>
where
E: cli::ConfigEnvProvider,
{
let discovery_started = clock.now();
match observability::record_config_load(observability::ConfigLoadPhase::DiagMode, clock, || {
let (result, outcome) = cli::resolve_json_and_layers_outcome_with_env(
context.parsed_cli,
context.matches,
context.config_env,
);
match result {
Ok(is_json_enabled) => Ok((is_json_enabled, outcome)),
Err(error) => Err(Box::new((error, outcome))),
}
}) {
Ok((is_json_enabled, outcome)) => {
cli::record_discovery_outcome(clock, discovery_started, &outcome);
let mode = DiagMode::from_json_enabled(is_json_enabled);
set_tracing_filter(startup_filter(mode, context.parsed_cli.verbose));
outcome.emit_diagnostics();
Ok(ResolvedDiagnosticMode {
mode,
discovered_layers: outcome.into_layers(),
})
}
Err(error_and_outcome) => {
let (err, outcome) = *error_and_outcome;
cli::record_discovery_outcome(clock, discovery_started, &outcome);
let fallback_filter = startup_filter(context.startup_mode, context.parsed_cli.verbose);
set_tracing_filter(fallback_filter);
outcome.emit_diagnostics();
Err(config_err_to_exit(
err.as_ref(),
context.startup_mode,
observability::DIAG_MODE_OPERATION,
))
}
}
}
/// Merge CLI values with the layers cached during diagnostic-mode resolution.
///
/// The merge is timed with the injected [`MonotonicClock`], applies the default
/// command, and maps configuration failures to a failure [`ExitCode`].
///
/// # Errors
///
/// Returns a failure [`ExitCode`] when merging the discovered layers with the
/// CLI values fails.
fn merge_cli_or_exit<E>(
context: &ConfigurationLoadContext<'_, E>,
resolution: ResolvedDiagnosticMode,
clock: &impl MonotonicClock,
) -> Result<cli::Cli, ExitCode>
where
E: cli::ConfigEnvProvider,
{
observability::record_config_load(observability::ConfigLoadPhase::Merge, clock, || {
let input = cli::CachedMergeInput::new(
context.parsed_cli,
context.matches,
context.config_env,
resolution.discovered_layers,
);
let (merged, events) = cli::merge_with_cached_file_layers_with_observer(input);
let mut observer = cli::TracingMergeObserver;
for event in events {
cli::MergeObserver::observe(&mut observer, event);
}
merged
})
.map(cli::Cli::with_default_command)
.map_err(|err| {
config_err_to_exit(
err.as_ref(),
resolution.mode,
observability::MERGE_OPERATION,
)
})
}
/// Emit the configuration-load metrics for one startup attempt.
///
/// Recording goes through the `metrics` façade, backed by the application's
/// in-process `DebuggingRecorder`.
fn record_config_load_metrics(elapsed: Duration, succeeded: bool) {
let outcome = if succeeded { "success" } else { "failure" };
metrics::histogram!(observability::STARTUP_CONFIG_LOAD_DURATION).record(elapsed.as_secs_f64());
metrics::counter!(observability::STARTUP_CONFIG_LOAD_COUNTER, "outcome" => outcome)
.increment(1);
}
#[cfg(test)]
#[path = "config_load_metrics_tests.rs"]
mod config_load_metrics_tests;