Skip to content

Commit b828bd3

Browse files
authored
Merge pull request #41 from NGWPC/jwade_hindcast_logs
Add hindcast orchestration logger to hindcasting workflow
2 parents d145b7a + 1c32c26 commit b828bd3

2 files changed

Lines changed: 63 additions & 16 deletions

File tree

python/nwm_fcst_mgr/forecast.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
OS_ENV_KEY_NGEN_LOG_FILE_PREFIX,
3333
OS_ENV_KEY_RESULTS_DIR,
3434
initialize_logger,
35+
initialize_hindcast_logger,
3536
set_os_env_key,
3637
)
3738

@@ -50,6 +51,9 @@
5051
# setup the logger
5152
logger, _ = initialize_logger()
5253

54+
# Set dedicated ewts_id for hindcast orchesetration logger.
55+
HINDCAST_LOGGER_ID = "hindcast_logger"
56+
5357

5458
class ConfigCache:
5559
"""
@@ -68,7 +72,6 @@ def __init__(self, valid_yaml: str = None, run_dir: str = None, no_valid: bool =
6872
raise ValueError(msg)
6973
self.valid_yaml = valid_yaml
7074
self.valid_config = load_yaml(valid_yaml)
71-
logger.info(f"Validation file loaded from: {valid_yaml}")
7275
self.gpkg_cats, self.gpkg_nexus, self.ngen_exe, self.gage0 = extract_config(
7376
self.valid_config, self.valid_yaml
7477
)
@@ -107,7 +110,7 @@ class ForecastExecutionManager:
107110
108111
Parameters
109112
----------
110-
real_path : str
113+
real_path : str
111114
Path to existing realization file
112115
config_cache : ConfigCache
113116
Instance of ConfigCache
@@ -420,7 +423,7 @@ def postprocess(self, suppress_output: bool = False) -> None:
420423
self.output_csv = Path(run_output_dir, self.gage0 + "_output.csv")
421424
output.to_csv(self.output_csv)
422425

423-
logger.info(f"Fcst-mgr NGEN run outputs saved at: {run_output_dir}")
426+
logger.info(f"Fcst-mgr NGEN postprocessing outputs saved at: {run_output_dir}")
424427

425428
self._status = RunStatus.POSTPROCESSED
426429
logger.status(
@@ -775,7 +778,11 @@ def run_hindcast(input_path, valid_yaml, fcst_run_name, cycle_interval, num_iter
775778
If provided, will be used for first hindcast cycle (hind_cycle=0)
776779
Subsequent cycles will use warm start states
777780
"""
778-
logger.info(f'Initializing hindcast runs from: {valid_yaml}')
781+
# Set up hindcast orchestration logger, initialized once the hindcast root directory is known
782+
hindcast_logger = None
783+
784+
# Buffer messages logged before orchestration log's directory is resolvable (before first build_fcst() call)
785+
pending_logs = [f'Initializing hindcast runs from: {valid_yaml}']
779786

780787
if valid_yaml is None:
781788
msg = "valid_yaml must be provided for hindcast run"
@@ -791,7 +798,7 @@ def run_hindcast(input_path, valid_yaml, fcst_run_name, cycle_interval, num_iter
791798
# Validate that all hindcast intervals fall on valid cycle hours for this configuration
792799
check_hind_intervals(input_path, hind_interval)
793800

794-
logger.info(f"Initializing hindcast runs at intervals: {hind_interval}")
801+
pending_logs.append(f"Initializing hindcast runs at intervals: {hind_interval}")
795802

796803
# Initialize previous hindcast cycle for coordinating warm starts
797804
prev_hind_cycle = 0
@@ -805,19 +812,19 @@ def run_hindcast(input_path, valid_yaml, fcst_run_name, cycle_interval, num_iter
805812
# Skip warm start for first hindcast, which will use the cold start state
806813
if hind_cycle != 0:
807814

808-
logger.info(f"Initializing warm start AnA run for hindcast iteration at {hind_cycle} hours")
815+
hindcast_logger.info(f"Initializing warm start AnA run for hindcast iteration at {hind_cycle} hours")
809816

810817
# Generate msw-mgr inputs for warm start run for hindcast iteration
811818
warm_start_real_path, warm_start_state = build_fcst(input_path=input_path, valid_yaml=valid_yaml,
812819
fcst_run_name=fcst_run_name, use_warm_start=True,
813820
hind_cycle=hind_cycle, prev_hind_cycle=prev_hind_cycle,
814821
save_state=True, load_state_from=prev_warm_start_state)
815-
logger.info(f"Warm start realization file for hindcast iteration at {hind_cycle} hours written to: {warm_start_real_path}")
822+
hindcast_logger.info(f"Warm start realization file for hindcast iteration at {hind_cycle} hours written to: {warm_start_real_path}")
816823

817824
# Execute warm start ngen run to generate hindcasting model states
818825
run_workflow(warm_start_real_path, config_cache, suppress_output=True)
819-
logger.info(f"Warm start run for hindcast iteration at {hind_cycle} hours completed")
820-
logger.info(f"Warm start state saved to {warm_start_state}")
826+
hindcast_logger.info(f"Warm start run for hindcast iteration at {hind_cycle} hours completed")
827+
hindcast_logger.info(f"Warm start state saved to {warm_start_state}")
821828

822829
# Update state to be used by warm start in next iteration
823830
prev_warm_start_state = warm_start_state
@@ -831,24 +838,37 @@ def run_hindcast(input_path, valid_yaml, fcst_run_name, cycle_interval, num_iter
831838
'hind_cycle': hind_cycle
832839
}
833840

834-
logger.info(f"Initializing hindcast run for iteration at {hind_cycle} hours")
841+
msg = f"Initializing hindcast run for iteration at {hind_cycle} hours"
842+
if hind_cycle == 0:
843+
pending_logs.append(msg)
844+
else:
845+
hindcast_logger.info(msg)
835846

836847
# Load from cold start state for first cycle if it's provided
837848
if hind_cycle == 0:
838849
if cold_start_state is not None:
839850
hind_kwargs['load_state_from'] = cold_start_state
840-
logger.info(f"Hindcast iteration at {hind_cycle} hours loading state from: {cold_start_state}")
851+
pending_logs.append(f"Hindcast iteration at {hind_cycle} hours loading state from: {cold_start_state}")
841852
# Otherwise, load from warm start state
842853
else:
843854
hind_kwargs['load_state_from'] = warm_start_state
844-
logger.info(f"Hindcast iteration at {hind_cycle} hours loading state from: {warm_start_state}")
855+
hindcast_logger.info(f"Hindcast iteration at {hind_cycle} hours loading state from: {warm_start_state}")
845856

846857
hind_real_path, _ = build_fcst(**hind_kwargs)
847-
logger.info(f"Hindcast realization file for iteration at {hind_cycle} hours written to: {hind_real_path}")
858+
859+
# Initialize hindcast orchestration logger after first build_fcst() call; hindcast root directory now resolvable
860+
if hindcast_logger is None:
861+
hindcast_root = Path(hind_real_path).parent.parent
862+
hindcast_logger = initialize_hindcast_logger(str(hindcast_root))
863+
# Flush pending logs
864+
for msg in pending_logs:
865+
hindcast_logger.info(msg)
866+
867+
hindcast_logger.info(f'Hindcast realization file for iteration at {hind_cycle} hours written to: {hind_real_path}')
848868

849869
# Run hindcasting period
850870
run_workflow(hind_real_path, config_cache)
851-
logger.info(f"Hindcast run for iteration at {hind_cycle} hours completed")
871+
hindcast_logger.info(f"Hindcast run for iteration at {hind_cycle} hours completed")
852872

853873
# Store previous hindcast cycle value to set next warm start duration
854874
prev_hind_cycle = hind_cycle

python/nwm_fcst_mgr/utils.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
import ewts
44
from datetime import datetime, timezone
5+
import os
56
from os import environ
67
from pathlib import Path
78

89
OS_ENV_KEY_RESULTS_DIR = "NGEN_RESULTS_DIR"
910
OS_ENV_KEY_NGEN_LOG_FILE_PREFIX = "NGEN_LOG_FILE_PREFIX"
11+
HINDCAST_LOGGER_ID = "HINDCAST"
12+
1013

1114
def set_os_env_key(key: str, val: str, override: bool = True) -> None:
1215
"""Set the value of the OS environment key.
@@ -43,6 +46,7 @@ def set_os_env_key(key: str, val: str, override: bool = True) -> None:
4346
LOG.info(f"Setting OS env key {repr(key)} to value {repr(val)}.")
4447
environ[key] = val
4548

49+
4650
def create_timestamp(date_only: bool = False, iso: bool = False, append_ms: bool = False) -> str:
4751
now = datetime.now(timezone.utc)
4852

@@ -59,6 +63,7 @@ def create_timestamp(date_only: bool = False, iso: bool = False, append_ms: bool
5963
else:
6064
return ts_base
6165

66+
6267
def initialize_logger(log_path: str | None = None, log_id: str | None = None) -> tuple[ewts.EwtsLogger, Path]:
6368
'''
6469
Set up logger.
@@ -88,12 +93,13 @@ def initialize_logger(log_path: str | None = None, log_id: str | None = None) ->
8893
log_file_dir = base_dir / "run-logs/fcst-mgr"
8994

9095
log_file_name = f"fcst_mgr_{create_timestamp()}.log"
91-
92-
9396

9497
# In case the logger was previously setup for bootstrapping
9598
ewts.logger.reset_logger(ewts.FCST_MGR_ID)
9699

100+
# In certain conditions the log dir does not yet exist
101+
os.makedirs(log_file_dir, exist_ok=True)
102+
97103
return ewts.logger.setup_logger(
98104
ewts.FCST_MGR_ID,
99105
level="INFO",
@@ -104,3 +110,24 @@ def initialize_logger(log_path: str | None = None, log_id: str | None = None) ->
104110
), (log_file_dir / log_file_name)
105111

106112

113+
def initialize_hindcast_logger(log_path: str) -> ewts.EwtsLogger:
114+
'''
115+
Set up the dedicated hindcast logger, which persists for the duration of a run_hindcast() workflow
116+
117+
Arguments
118+
---------
119+
log_path: Directory to write hindcast log (hindcast run's root folder)
120+
121+
Returns
122+
-------
123+
ewts.EwtsLogger
124+
Instance of the EWTS logger.
125+
'''
126+
return ewts.logger.setup_logger(
127+
HINDCAST_LOGGER_ID,
128+
level="INFO",
129+
log_dir=Path(log_path),
130+
log_file_name="fcst_mgr_hindcast.log",
131+
running_in_ngen=False,
132+
enabled=True,
133+
)

0 commit comments

Comments
 (0)