@@ -261,6 +261,36 @@ struct ParsedS3Config {
261261 prefix : Option < String > ,
262262}
263263
264+ #[ derive( Debug ) ]
265+ struct ResolvedEnvConfig {
266+ enabled : bool ,
267+ trace_dir : PathBuf ,
268+
269+ // None means the underlying RotatingWriter builder owns the default.
270+ rotation_period : Option < Duration > ,
271+
272+ max_total_size : u64 ,
273+ max_file_size : u64 ,
274+ task_tracking_enabled : bool ,
275+
276+ // Optional config: None means do not set a runtime name.
277+ runtime_name : Option < String > ,
278+
279+ // Optional integration: None means do not configure S3 upload.
280+ s3 : Option < ParsedS3Config > ,
281+
282+ cpu_profile_enabled : bool ,
283+
284+ // None means CpuProfilingConfig::default() owns the sample rate.
285+ cpu_sample_hz : Option < u64 > ,
286+
287+ schedule_profile_enabled : bool ,
288+ task_dump_enabled : bool ,
289+
290+ // None means TaskDumpConfig::default() owns the idle threshold.
291+ task_dump_idle_threshold : Option < Duration > ,
292+ }
293+
264294struct RuntimeEnvConfig {
265295 task_tracking_enabled : bool ,
266296 runtime_name : Option < String > ,
@@ -310,6 +340,41 @@ fn parse_env_config(env: &impl EnvSource) -> ParsedEnvConfig {
310340 }
311341}
312342
343+ fn resolve_env_config ( parsed : ParsedEnvConfig ) -> ResolvedEnvConfig {
344+ let max_total_size = parsed
345+ . max_total_size
346+ . unwrap_or_else ( || DEFAULT_MAX_DISK_USAGE_MB . saturating_mul ( BYTES_PER_MIB ) ) ;
347+ let max_file_size = parsed
348+ . max_file_size
349+ . unwrap_or_else ( || derive_max_file_size ( max_total_size) ) ;
350+
351+ ResolvedEnvConfig {
352+ enabled : parsed. enabled . unwrap_or ( DEFAULT_ENABLED ) ,
353+ trace_dir : parsed
354+ . trace_dir
355+ . unwrap_or_else ( || PathBuf :: from ( DEFAULT_TRACE_DIR ) ) ,
356+ rotation_period : parsed. rotation_period ,
357+ max_total_size,
358+ max_file_size,
359+ task_tracking_enabled : parsed
360+ . task_tracking_enabled
361+ . unwrap_or ( DEFAULT_TASK_TRACKING_ENABLED ) ,
362+ runtime_name : parsed. runtime_name ,
363+ s3 : parsed. s3 ,
364+ cpu_profile_enabled : parsed
365+ . cpu_profile_enabled
366+ . unwrap_or ( DEFAULT_CPU_PROFILE_ENABLED ) ,
367+ cpu_sample_hz : parsed. cpu_sample_hz ,
368+ schedule_profile_enabled : parsed
369+ . schedule_profile_enabled
370+ . unwrap_or ( DEFAULT_SCHEDULE_PROFILE_ENABLED ) ,
371+ task_dump_enabled : parsed
372+ . task_dump_enabled
373+ . unwrap_or ( DEFAULT_TASK_DUMP_ENABLED ) ,
374+ task_dump_idle_threshold : parsed. task_dump_idle_threshold ,
375+ }
376+ }
377+
313378struct EnvSourceParser < S > ( S ) ;
314379
315380impl < S > EnvSourceParser < S > {
@@ -540,7 +605,7 @@ impl Dial9Config {
540605 }
541606
542607 fn from_env_source ( env : & impl EnvSource ) -> Self {
543- let ParsedEnvConfig {
608+ let ResolvedEnvConfig {
544609 enabled,
545610 trace_dir,
546611 rotation_period,
@@ -554,30 +619,21 @@ impl Dial9Config {
554619 schedule_profile_enabled,
555620 task_dump_enabled,
556621 task_dump_idle_threshold,
557- } = parse_env_config ( env) ;
558-
559- let max_total_size = max_total_size
560- . unwrap_or_else ( || DEFAULT_MAX_DISK_USAGE_MB . saturating_mul ( BYTES_PER_MIB ) ) ;
561- let max_file_size = max_file_size. unwrap_or_else ( || derive_max_file_size ( max_total_size) ) ;
622+ } = resolve_env_config ( parse_env_config ( env) ) ;
562623
563624 let runtime_config = RuntimeEnvConfig {
564- task_tracking_enabled : task_tracking_enabled . unwrap_or ( DEFAULT_TASK_TRACKING_ENABLED ) ,
625+ task_tracking_enabled,
565626 runtime_name,
566- cpu_profile_enabled : cpu_profile_enabled . unwrap_or ( DEFAULT_CPU_PROFILE_ENABLED ) ,
627+ cpu_profile_enabled,
567628 cpu_sample_hz,
568- schedule_profile_enabled : schedule_profile_enabled
569- . unwrap_or ( DEFAULT_SCHEDULE_PROFILE_ENABLED ) ,
570- task_dump_enabled : task_dump_enabled. unwrap_or ( DEFAULT_TASK_DUMP_ENABLED ) ,
629+ schedule_profile_enabled,
630+ task_dump_enabled,
571631 task_dump_idle_threshold,
572632 } ;
573633
574634 let builder = Self :: builder ( )
575- . enabled ( enabled. unwrap_or ( DEFAULT_ENABLED ) )
576- . base_path (
577- trace_dir
578- . unwrap_or_else ( || PathBuf :: from ( DEFAULT_TRACE_DIR ) )
579- . join ( "trace.bin" ) ,
580- )
635+ . enabled ( enabled)
636+ . base_path ( trace_dir. join ( "trace.bin" ) )
581637 . max_file_size ( max_file_size)
582638 . max_total_size ( max_total_size)
583639 . maybe_rotation_period ( rotation_period) ;
@@ -899,6 +955,39 @@ mod tests {
899955 assert_eq ! ( parsed. task_dump_idle_threshold, None ) ;
900956 }
901957
958+ #[ test]
959+ fn env_resolution_applies_only_from_env_owned_defaults ( ) {
960+ let resolved = resolve_env_config ( parse_env_config ( & FakeEnv :: default ( ) ) ) ;
961+ let supported_profiling = cfg ! ( all( target_os = "linux" , feature = "cpu-profiling" ) ) ;
962+
963+ assert_eq ! ( resolved. enabled, DEFAULT_ENABLED ) ;
964+ assert_eq ! ( resolved. trace_dir, PathBuf :: from( DEFAULT_TRACE_DIR ) ) ;
965+ assert_eq ! (
966+ resolved. max_total_size,
967+ DEFAULT_MAX_DISK_USAGE_MB * BYTES_PER_MIB
968+ ) ;
969+ assert_eq ! (
970+ resolved. max_file_size,
971+ derive_max_file_size( resolved. max_total_size)
972+ ) ;
973+ assert_eq ! (
974+ resolved. task_tracking_enabled,
975+ DEFAULT_TASK_TRACKING_ENABLED
976+ ) ;
977+ assert_eq ! ( resolved. cpu_profile_enabled, supported_profiling) ;
978+ assert_eq ! ( resolved. schedule_profile_enabled, supported_profiling) ;
979+ assert_eq ! ( resolved. task_dump_enabled, DEFAULT_TASK_DUMP_ENABLED ) ;
980+
981+ // Optional config/integrations remain absent unless explicitly requested.
982+ assert_eq ! ( resolved. runtime_name, None ) ;
983+ assert ! ( resolved. s3. is_none( ) ) ;
984+
985+ // Delegated defaults remain unset so their underlying config types own them.
986+ assert_eq ! ( resolved. rotation_period, None ) ;
987+ assert_eq ! ( resolved. cpu_sample_hz, None ) ;
988+ assert_eq ! ( resolved. task_dump_idle_threshold, None ) ;
989+ }
990+
902991 #[ test]
903992 fn env_parses_trimmed_values ( ) {
904993 let parsed = parse_env_config (
0 commit comments