There is a general need for application programs to provide the user with informative messages about:
This chapter describes two subroutine libraries which can be used for this purpose. They are:
These are fully described in SUN/104.
The obvious way to produce messages in Fortran programs is by WRITE and PRINT statements. It is possible to construct the text of a message from various components, including numbers, formatted in a CHARACTER variable using the internal WRITE statement. The resulting message may then be displayed. However, it is sometimes difficult to format numerical output in its most concise form. To do this in-line each time a message is sent to the user would be very inconvenient and justifies the provision of a dedicated set of subroutines. These considerations led to the production Message Reporting System.
The primary message reporting subroutine is MSG_OUT. It has a calling sequence of the form:
where PARAM is a character string giving the name of the message, TEXT is a character string giving the message text, and STATUS is the integer global status value.
It sends the message string, TEXT, to the standard output stream. This will normally be the user’s terminal, but is the log file for a batch job. The maximum message length is 200 characters. If it exceeds this, it is truncated with an ellipsis, i.e. ‘…’, but no error results.
Here is an example of using MSG_OUT:
It is sometimes useful to add blank lines for clarity. MSG_BLANK does this:
Messages can also be stored in a character variable, rather than being output. MSG_LOAD does this.
It is sometimes useful to have varying levels of message output which may be controlled by the user. This is achieved by MSG_OUTIF:
Here, the first argument is the ‘priority’ associated with the message. It has three possible values, which represent filter levels:
(I know this looks wrong on a first reading, but ‘quiet’ messages are messages that are output in ‘quiet’ mode, and are therefore the loudest!) The default is MSG__NORM. It may be modified by MSG_IFSET, for example:
Applications often need to include the values of Fortran variables in output messages. This is done in the Message System using tokens embedded in the message text. For example, a program which measures the intensity of an emission line in a spectrum can output its result by:
Here, MSG_SETR assigns the result, stored in the REAL variable FLUX, to the message token named ‘FLUX’. The token string is then included in the message text by prefixing it with the up-arrow, ‘’, escape character. The usual set of similar routines is provided to handle the other standard data types.
An additional feature of the MSG_SETx routines is that calls using an existing token name will result in the value being appended to any previously assigned token string. Here is the previous example written to exploit this feature:
Note that repeated calls append values with no separator, and hence a leading space is needed in the FUNITS string to separate the flux value and its units in the expanded message.
Sometimes a specific format is required; for example, the message may form part of a table. MSG_FMTx is a set of subroutines of the form:
where FORMAT is a valid Fortran 77 format string which can be used to encode the supplied value, VALUE. As for MSG_SETx, x corresponds to each of the five standard Fortran 77 data types — D, R, I, L and C.
Sometimes it is necessary to include the message token escape character, ‘’, literally in a message. When the character is not the last in a message string, it can be included literally by duplicating it. When it is immediately followed by a blank, or is at the end of the MSG_OUT text, it is included literally. Escape characters and token names will also be output literally if they appear within the value assigned to a message token; i.e. message token substitution is not recursive.
In calls to MSG_OUT and MSG_LOAD, the message name is the name of a message parameter which is associated with the message text. This name should be no more than 15 characters long and may be associated with a message specified in the interface file. When the message parameter is specified in the interface file, this text is used in preference to that given in the argument list.
Here is an example of using MSG_OUT:
This will generate the message:
If the message parameter, ‘RD_TAPE’, is associated with a different text string in the interface file, e.g.
then the output message would be the one defined in the interface file:
This enables ADAM applications to support foreign languages.
We may need to refer to a program parameter in a message. There are two kinds of reference required:
We can include references of these kinds by prefixing parameter names with an escape character of which there are three: ‘’, ‘%’, ‘$’. To include a parameter’s keyword, prefix its name with a ‘%’ character, as in:
If the keyword of parameter ET is ‘EXPOSURE_TIME’, the output generated by this call is:
To include the name of an object, device, or file associated with a parameter, prefix its name with a ‘$’ character, as in:
If parameter DATASET is associated with an object called SWP1234, this would produce the output:
Sometimes a parameter name is contained in a variable. In order to use it in a message, a message token can be associated with its name. For example:
In this example, the escape sequence ‘’ prefixes the token name, ‘PAR’. The sequence ‘PAR’ gets replaced by the contents of the character string contained in the variable PNAME; this, being prefixed by ‘%’, then gets replaced in the final message by the keyword associated with the parameter.
Routine MSG_IFSET sets the filter level for conditional message output. Routine MSG_IFGET also sets it, but gets its value from the parameter system:
where PNAME is the parameter name. Always use the same name, MSG_FILTER, for this purpose. The three acceptable strings are:
Any other value will result in an error report and the status value being set to MSG__INVIF.
Although the Message System could be used for reporting errors, there are a number of reasons why a separate facility should be provided:
This can lead to several error reports arising from a single failure.
These considerations have led to the design and implementation of a set of routines which form the Error Reporting System.
The recommended method of indicating when errors have occurred in Starlink software is to use an integer status value in each subroutine argument list. This inherited status argument, say STATUS, should always be the last argument and every routine should check its value on entry. The ADAM Error Strategy is as follows:
Note that it is often useful to use a status argument and inherited status checking in routines which ‘cannot fail’. This prevents them executing, possibly producing a run-time error, if their arguments contain rubbish after a previous error. Every piece of software that calls such a routine is then saved from making an extra status check. Furthermore, if the routine is later upgraded it may acquire the potential to fail, and so a status argument will subsequently be required. If a status argument is included initially, existing code which calls the routine will not need changing.
The routine used to report errors is ERR_REP. It has a calling sequence of the form:
where PARAM is the error message name, TEXT is the error message text, and STATUS is the inherited status. These arguments are similar to those used in the Message System routine MSG_OUT.
The error message name, PARAM, should be a globally unique identifier for the error report with the form:
for routines in an application, or:
for routines in a subroutine library. In the former case, routn is the name of the application routine from which ERR_REP is being called, and message is a sequence of characters uniquely identifying the error report within that routine. In the latter case, fac_routn is the full name of the routine from which ERR_REP is being called, and message is a sequence of characters unique within that routine. These naming conventions are designed to ensure that each error report made within a complete software system has a unique error name associated with it.
Here is a simple example of error reporting where part of the application code detects an invalid value of some kind, sets STATUS, reports the error, and then aborts:
This sequence of three operations:
is the standard response to an error condition and should be adopted by all software which uses the Error System.
Note that ERR_REP differs from MSG_OUT in that ERR_REP will execute regardless of the input value of STATUS. Although the Starlink convention is for routines not to execute if their status argument indicates a previous error, the Error System routines obviously cannot behave in this way if their purpose is to report these errors.
Message tokens can be used in ERR_REP in the same way as in MSG_OUT.
In the following example, part of an application makes a series of routine calls:
Each routine uses the inherited status strategy and reports errors by calling ERR_REP. If an error occurs within any of them, STATUS will be set to an error value and inherited status checking by all subsequent routines will cause them not to execute. Thus, it becomes unnecessary to check for an error after each routine call, and a single check at the end of the sequence of calls is all that is required to handle correctly any error condition that may arise. Because an error report will already have been made by the routine that failed, it is usually sufficient simply to abort if an error arises in a sequence of routine calls.
It is important to distinguish the case where a called subroutine sets STATUS and makes its own error report, as above, from the case where STATUS is set explicitly as a result of a directly detected error, as in the previous example. If the error reporting strategy is to function correctly, then responsibility for reporting the error must lie with the routine which modifies the status argument. The golden rule is therefore:
If STATUS is explicitly set to an error value, then an accompanying call to ERR_REP must be made.
Unless there are good documented reasons why this cannot be done, routines which return a bad status value and do not make an accompanying error report should be regarded as containing a bug1.
Normally, set status values to the global constants SAI__OK and SAI__ERROR. However, when writing subroutine libraries it is useful to have a larger number of error codes available and to define these in a separate include file. The naming convention:
should be used for the names of error codes where fac is the three-character facility prefix and ecode is up to five alphanumeric characters of error code name. Note the double underscore used in this naming convention. The include file should be referred to by the name fac_ERR, e.g.
where in this case the facility name is SGS (Simple Graphics System). These symbolic constants should be defined at the beginning of every routine which requires them, prior to the declaration of any subroutine arguments or local variables.
The purpose of error codes is to enable the status argument to indicate that an error has occurred by having a value which is not equal to SAI__OK. By using a set of pre-defined error codes the calling routine is able to test the returned status to distinguish between error conditions which may require different action. Generally, it is not necessary to define a large number of error codes which would allow a unique value to be used every time an error report is made. It is sufficient to be able to distinguish the important classes of error which may occur. Examples of existing software can be consulted as a guide in this matter.
Software from outside a package which defines a set of error codes may use that package’s codes to test for specific error conditions arising within that package. However, with the exception of the SAI__ codes, it should not assign these values to the status argument. To do so could cause confusion about which package detected the error.
The purpose of an error message is to be informative and it should therefore provide as much relevant information about the context of the error as possible. It should not be misleading or contain irrelevant information. Particular care is necessary when reporting errors from routines which might be called by a wide variety of software. They should not make unjustified assumptions. For example, in a routine that adds two arrays, the report:
would be preferable to:
if the same routine could be called to add two spectra!
Normally it is adequate to report an error when is first detected, followed by a further report from the ‘top-level’ routine. Only include routine names in error reports if they appear in documentation.
The Error System can defer the output of a message to the user, and this allows the final delivery of error messages to be controlled. This is done by the routines ERR_MARK, ERR_RLSE, ERR_FLUSH and ERR_ANNUL. This section describes their functions and how they are used.
The purpose of deferred error reporting can be illustrated by the following example. Consider a routine, HELPER, which detects an error during execution. It reports the error to its caller, giving as much contextual information as it can. It also returns an error status, enabling the caller to react appropriately. However, what may be considered an ‘error’ in HELPER, e.g. an ‘end of file’ condition, may be considered by the caller to be something that can be handled without informing the user, e.g. by terminating its input. Thus, although HELPER will always report the error, it is not always necessary for the associated error message to reach the user. Deferred error reporting enables programs to handle such errors internally.
Suppose HELPER reports an error. At this point, the error message may, or may not, have been received by the user — this will depend on the environment and on whether the caller deferred the error report. HELPER should not ensure delivery of the message to the user; its responsibility ends when it aborts, and responsibility for handling the error condition passes to the caller.
Suppose HELPER is called by HELPED which defers error messages so it can decide how to handle errors. It does this by calling ERR_MARK before HELPER. This ensures that subsequent error messages are deferred and stored in an ‘error table’. It also starts a new ‘error context’ which is independent of previous error messages or tokens. Routine ERR_RLSE returns to the previous context, whereupon any messages in the new error context are transferred to the previous context. In this way, no existing error messages can be lost through deferral. ERR_MARK and ERR_RLSE should always be called in matching pairs and can be nested.
After deferring the error messages, HELPED can handle the error condition in one of two ways:
When an application finally ends, the value of the status argument will reflect whether or not it finished with an error condition. At this point, any remaining error messages will be delivered automatically to the user.
In calls to ERR_REP, the error name is the name of a message parameter which is associated with the error message text. Like the message parameters used in MSG_OUT and MSG_LOAD, those used in ERR_REP may be associated with an error message specified in the interface file as well as in the argument list.
1For historical reasons there are still many routines in ADAM which set a status value without making an accompanying error report — these are gradually being corrected. If such a routine is used before it has been corrected, then the strategy outlined here is recommended. It is advisable not to complicate new code by attempting to make an error report on behalf of the faulty subroutine. If appropriate, please tell the relevant support person about the problem.