@z This file was created by Aleksandar Donev as part of the Network Optimization project. Feel free to use any portion of it and contact me at donev@pa.msu.edu @x \Title{Error Handling Routines} \author{Aleksandar Donev} \date{February 2000} @*0 Module |Error_Handling|. This module contains functions that handle errors of different levels of seriousness (messages, warnings, non-critical errors, critical errors). Error-handling is a tricky thing, and in general the behaviour of the program under different conditions needs to be controlled. Here I give the user this control via the variables in the namelist |ProgramOptions| from the module |Initialization_Termination|. It is assumed that this namelist has already been read via the routines in that module (there is some circular thinking in this module structure, but the three modules |Initialization_Termination|, |Error_Handling| and |System_Monitors| should always go together). In fact, the module |Error_Handling| is more than just an error-handling library, it controls I/O in a more general way. One thing to notice is that decided to put all namelists in one file, identified by the fixed name |options_file| and opened with the fixed unit |program_options_unit|. This may not be the best design--it might be better to let each routine read its namelist from its own file. But in general a |REWIND| statement before reading a namelist will insure that the order of the namelists in the options file does not matter. The options file is opened in |InitializeErrorHandling| and closed in |TerminateErrorHandling| along with the other files. Here is the explanation of the important public variables in this module: \begin{description} \item[|error_status|] is a flag error indicator. A common way to handle non-critical exceptions, such as numerical instability or rounding error, is to have a flag that is raised (here to a non-zero error value) and kept there until someone explicitly lowers it. I could not come up with a descent design of such a scheme for this library, and in reality the error handling routines provided here seemed enough. Never-the-less, this variable can either be used as a temporary error indicator or as a flag if the user does it himself. \item[|options_file|, |print_file|, |log_file| and |null_file|] are the names of the file where the namelists with all the choice parameters are, the optional file where printed output is redirected, the file where statistics are logged, and a temporary scratch file. The options file can not be changed, since all the choices are in there, while the log and print file can be changed via the options file. Note that {\em an empty-string |print_file| means that printing is done to standard output}. A non-zero name for the print file is essentially equivalent to piping the printed output to a file under UNIX, and in fact with the {\tt tee} command on UNIX one can both see the file and redirect it. The log file can be used to store statics which can be voluminous and stored for later analysis. The library will log most "important" events in that file and avoid printing as much as possible. The null file is deleted at the end and is equivalent to \begin{verbatim} \dev\null \end{verbatim} on UNIX. \item[|append|] controls whether the files are appended to or overwritten. \item[|stdin|, |stdout|, |log_unit|, |print_unit| and |null_unit|] should not be used by the user since they are reserved for the above files. On almost all systems these can be accomodated in units 1 thru 10, so use units higher than 10 for your own files. \item[|_log_unit| , |_print_unit|, |log_level| and |print_level|] \\ Three kinds of events that warrant I/O are recognized by this module, simple messages (statistics and such), warnings (for example floating-point exceptions or numerical instability--warranting a messgae and maybe user action) and errors, which can be critical (always warranting |STOP|ing) or non-critical (warranting |STOP|ing or some user action). In all I/O commands in files in this library each I/O statement should be classified and the corresponding logging or printing unit used. So, {\em never use |*|, 5 or 6 for |UNIT|}. For example, printing messages should always be done with |WRITE(UNIT=message_print_unit)|. Now, how these logging and printing units are chosen depend on the values of |log_level| and |print_level|. The events are ordered and numbered from 4-1 as messages, warnings, non-critical erros and critical errors. |log_level| gives the highest number for the events to be logged, and |print_level| for printing. For example, to log all events but only print errors set |log_level=4| and |print_level=2|. \item[|warning_action| and |non_critical_action|] determine what happens for warnings and non-critical errors. For warnings the choices are |"Pause"| or |"Continue"| (first-letter only non-case sensitive) and for non-critical errors |"Stop"| is added to this list (in reality one should never continue on an error...) \end{description} The routines |Warning|, |NonCriticalError| and |CriticalError| handle exceptions (for messages use your own |WRITE(UNIT=message_log_unit)| or |WRITE(UNIT=message_print_unit)|. Their interfaces are simple and self-explanatory. They accept a string |message| to be logged and printed and a string argument |caller| telling where the exception occured and an optional argument |action| telling what action to take (critical errors always abort execution). @f log _log @f print _print @a MODULE Error_Handling @; USE Precision @; // Kind parameters IMPLICIT NONE @; PUBLIC :: InitializeErrorHandling, TerminateErrorHandling, & Warning, NonCriticalError, CriticalError @; PRIVATE @; INTEGER, PUBLIC, SAVE :: error_status=0 @; // This can be used as an error flag CHARACTER(LEN=100), PUBLIC, PARAMETER :: options_file=NO_OPTION_FILE, & null_file="ScratchFile.log" @; // Public as well CHARACTER(LEN=100), PUBLIC, SAVE :: log_file="Network.log" @; // Log file CHARACTER(LEN=100), PUBLIC, SAVE :: print_file="" @; // No print file by default LOGICAL, PUBLIC, SAVE :: append=.FALSE. @; // Clear files by default INTEGER, PARAMETER, PUBLIC :: stdin=5, stdout=6, stderr=0 @; INTEGER, PARAMETER, PUBLIC :: null_unit=1, log_unit=2 @; INTEGER, SAVE, PUBLIC :: print_unit=stdout @; // By default print to {\tt stdout} INTEGER, PARAMETER, PUBLIC :: program_options_unit=10 @; // |NAMELIST| file for program options INTEGER, PUBLIC, SAVE :: log_level=3, print_level=4 @; // Log all messages, print warnings and errors only by default INTEGER, PUBLIC, SAVE :: message_log_unit=log_unit, message_print_unit=null_unit, & warning_log_unit=log_unit, warning_print_unit=stdout, & error_log_unit=log_unit, error_print_unit=stdout @; // Units to be used in all |WRITE| statements in the program CHARACTER(LEN=1), PUBLIC, SAVE :: warning_action="C", non_critical_action="P" @; // Continue on warnings and pause for non-critical errors by default! CONTAINS @; @@; @@; @@; END MODULE Error_Handling @; @*1 Initialization. We need to open some files for logging and for garbage output. I choose not to check for errors in opening and closing some of the files here since this is unlikely to happen and will just complicate the code a lot: @%% @= SUBROUTINE InitializeErrorHandling() @; IMPLICIT NONE @; @%% INTEGER, INTENT(IN), OPTIONAL :: logging_level, printing_level @; @%% CHARACTER(LEN=*), INTENT(IN), OPTIONAL :: print_file @; @%% LOGICAL, INTENT(IN), OPTIONAL :: appending @; CHARACTER(LEN=15) :: file_positioning @; @%% IF(PRESENT(logging_level)) @~ log_level=logging_level @; @%% IF(PRESENT(printing_level)) @~ print_level=printing_level @; @%% IF(PRESENT(appending)) THEN @; IF(append) THEN @; file_positioning="APPEND" @; // Append to the files from previous runs ELSE @; file_positioning="REWIND" @; // Start files from scratch END IF @; @%% ELSE @; @%% file_positioning="REWIND" @; // From scratch by default @%% END IF @; IF(print_file!="") THEN @; // We don't want any printout to screen--background jobs print_unit=3 @; // Usually a free unit OPEN(UNIT=print_unit, FILE=TRIM(print_file), STATUS="UNKNOWN", & ACCESS="SEQUENTIAL", ACTION="WRITE", POSITION=file_positioning) @; // Print file ELSE @; print_unit=stdout @; // Reset the value just in case END IF @; OPEN(UNIT=log_unit, FILE=TRIM(log_file), STATUS="UNKNOWN", & ACCESS="SEQUENTIAL", ACTION="WRITE", POSITION=file_positioning) @; // Log file OPEN(UNIT=null_unit, FILE=TRIM(null_file), STATUS="UNKNOWN", & ACCESS="SEQUENTIAL", ACTION="WRITE", POSITION=file_positioning) @; // A {\tt /dev/null}-like unit. But we may decide to keep it later after all... _SetUnit(message, log, 4) @; _SetUnit(message, print, 4) @; _SetUnit(warning, log, 3) @; _SetUnit(warning, print, 3) @; _SetUnit(error, log, 2) @; _SetUnit(error, print, 2) @; END SUBROUTINE InitializeErrorHandling @; @ The logging and printing units for different events depend on the values of |log_level| and |print_level|: @m _SetUnit(_type, _action, level) @; IF (_action@e@&_level>=level) THEN @; _type@e@&_@e@&_action@e@&_unit=_action@e@&_unit @; ELSE @; _type@e@&_@e@&_action@e@&_unit=null_unit @; END IF @; @*1 Termination. This routine does closing of files and such: @%% @= SUBROUTINE TerminateErrorHandling(keep_null) @; IMPLICIT NONE @; LOGICAL, INTENT(IN), OPTIONAL :: keep_null @; CLOSE(unit=log_unit) @; // Close log file IF(print_unit!=stdout) @~ CLOSE(UNIT=print_unit) @; // Close print file if it is a file IF(PRESENT(keep_null)) THEN @; IF(keep_null) THEN @; CLOSE(UNIT=null_unit, STATUS="KEEP") @; ELSE CLOSE(UNIT=null_unit, STATUS="DELETE") @; END IF @; ELSE @; CLOSE(UNIT=null_unit, STATUS="DELETE") @; // Delete the null file by default END IF @; END SUBROUTINE TerminateErrorHandling @; @*1 Error Reporting. We will have both critical and non-critical errors. Critical always terminate execution, while for non-critical the user can choose a global or one-time choice of handling. @ A few nice macros to avoid rewriting the same message: @m _RecordException(_Error, out_unit) @; WRITE(UNIT=out_unit,FMT="(A,A,A)", ADVANCE="NO") _Error," (!!!): ",message IF(PRESENT(caller)) @~ WRITE(UNIT=out_unit,FMT="(A,A)") " occured in: ",caller @m _StopExecution @: WRITE(UNIT=error_log_unit, FMT="(A)") "Stopping program with hard breakdown..." @; WRITE(UNIT=error_print_unit, FMT="(A)") "Stopping program with hard breakdown..." @; STOP @; @ @= SUBROUTINE Warning(message, caller, action) @; IMPLICIT NONE @; CHARACTER(LEN=*), INTENT(IN) :: message @; // A message to log and display--not |OPTIONAL| CHARACTER(LEN=*), INTENT(IN), OPTIONAL :: caller @; // Where did the error occur? CHARACTER(LEN=*), INTENT(IN), OPTIONAL :: action @; // What to do, either |"Pause"| or |"Continue"|. By default it will go on, as determined by |warning_action|. CHARACTER(LEN=1) :: local_action @; _RecordException("Warning", warning_log_unit) @; // Log this event _RecordException("Warning", warning_print_unit) @; // Also display IF(PRESENT(action)) THEN @; local_action=action[1:1] @; ELSE @; local_action=warning_action @; END IF @; SELECT CASE(local_action) @; CASE('P','p') @; // Pause PAUSE @; CASE DEFAULT @; // Continue by default RETURN @; ENDSELECT @; END SUBROUTINE Warning @; SUBROUTINE NonCriticalError(message, caller, action) @; IMPLICIT NONE @; CHARACTER(LEN=*), INTENT(IN) :: message @; // A message to log and display--not |OPTIONAL| CHARACTER(LEN=*), INTENT(IN), OPTIONAL :: caller @; // Where did the error occur? CHARACTER(LEN=*), INTENT(IN), OPTIONAL :: action @; // What to do, either |"Pause"|, |"Stop"| or |"Continue"|. By default it will |PAUSE|, as determined by |non_critical_action|. CHARACTER(LEN=1) :: local_action @; IF(PRESENT(action)) THEN @; local_action=action[1:1] @; ELSE @; local_action=non_critical_action @; END IF @; _RecordException("Non-Critical error", error_log_unit) @; // Log this event _RecordException("Non-Critical error", error_print_unit) @; // Also display SELECT CASE(local_action) @; CASE('C','c') @; // Continue execution RETURN @; CASE('S','s') @; // Stop execution _StopExecution @; CASE DEFAULT @; // Pause by default PAUSE @; ENDSELECT @; END SUBROUTINE NonCriticalError @; SUBROUTINE CriticalError(message, caller) @; IMPLICIT NONE @; CHARACTER(LEN=*), INTENT(IN) :: message @; // A message to log and display--not |OPTIONAL| CHARACTER(LEN=*), INTENT(IN), OPTIONAL :: caller @; // Where did the error occur? _RecordException("Critical error", error_log_unit) @; // Log this event _RecordException("Critical error", error_print_unit) @; // Also display _StopExecution @; // Abort execution END SUBROUTINE CriticalError @;