2 MSG – Message Reporting System

2.1 Overview

The most obvious way of producing informational messages from within Fortran or C application programs is through formatted printf, WRITE and PRINT statements. However:

• It is generally considered a good idea in a large system to direct all output through a single routine to improve portability and to make re-direction of output easier.
• Some environments, such as ADAM, require output via a ‘user-interface’ program and not direct to the terminal.
• It is sometimes difficult to format numerical output in its most concise form within textual messages. 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 have led to the design and implementation of a set of subroutines which form the Message Reporting System. The Message System subroutines have names of the form

msgName      [C]
MSG_name     [Fortran]

where name indicates what the subroutine does and follows the standard Starlink naming convention for C and Fortran. This document will provide examples in both languages, but subroutine names in the text will use the C naming convention.

2.2 Reporting messages

The primary message reporting subroutine is msgOut. It has a calling sequence of the form:

msgOut( param, text, status );

CALL MSG_OUT( PARAM, TEXT, STATUS )

where the argument 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 subroutine status value (a pointer to an int in C). msgOut sends the message string, text, to the standard output stream which will normally be the user’s terminal.

The subroutine msgOut uses the Starlink convention of inherited status. This means that calls to msgOut will not output the message unless the given value of status is equal to SAI__OK. If an error is encountered within the subroutine, then status is returned set to an error value. (The global constant SAI__OK is defined in the include file sae_par.h (SAE_PAR for Fortran). The use of this global constant and of inherited status are discussed in detail in §3.2.)

The maximum length for an output message is 300 characters. If it exceeds this length, then the message is truncated with an ellipsis, i.e. “…”, but no error will result.

By default, messages are split so that output lines do not exceed 79 characters – the split is made on word boundaries if possible. The maximum output line size can be altered using tuning parameters (see §4).

It is recommended that, within the application, the message name, param, should be a unique identifier for the message string, text. However, the message name serves no useful purpose within a stand-alone application and is often given as a blank string. In ADAM applications the message name has a specific purpose which is discussed in detail in Appendix E.2.

Here is an example of using msgOut:

msgOut( "EXAMPLE_MSGOUT", "An example of msgOut", status );

CALL MSG_OUT( ’EXAMPLE_MSGOUT’, ’An example of MSG_OUT.’, STATUS )

It is sometimes useful to intersperse blank lines amongst lines of textual output for clarity. This could be done using calls to msgOut, e.g.
msgOut( "MSG_BLANK", "", status );

CALL MSG_OUT( ’MSG_BLANK’, ’ ’, STATUS )

For convenience, the subroutine msgBlank has been provided for this purpose, e.g.
msgBlank( status );

CALL MSG_BLANK( STATUS )

The status argument in msgBlank behaves in the same way as the status argument in msgOut.

2.3 Conditional message reporting

It is sometimes useful to have varying levels of message output which may be controlled by the user of an application. Instances where this facility might be of use are:

• the ability to switch on informational messages for novice users which can later be switched off as the user becomes familiar with the application;
• the ability to switch off unnecessary informational messages when an application is run in batch mode, or within a command procedure;
• the ability to output detailed information from an application on request, say from within an iterative procedure.

Conditional message output is achieved explicitly in the Message Reporting System using the subroutine msgOutif to assign a “priority” to the message, e.g.

msgOutif( MSG__NORM, "", "A conditional message", status );

CALL MSG_OUTIF( MSG__NORM, ’ ’, ’A conditional message’, STATUS )

Here, the first argument is the “priority” associated with the message and can be any one of twenty four levels which are represented by symbolic constants defined in the include file msg_par.h (MSG_PAR) (see §6):
MSG__QUIET
– quiet mode, high priority;
MSG__NORM
– normal mode, normal priority (default);
MSG__VERB
– verbose mode, low priority.
MSG__DEBUG
– debug mode, lowest priority
MSG__DEBUGnn
– multiple debug modes. 1 to 20.

Whether or not the message will be output depends upon the “conditional message output filter” which may be set using the subroutine msgIfset. e.g.

msgIfset( MSG__QUIET, status );

CALL MSG_IFSET( MSG__QUIET, STATUS )

The first argument of msgIfset is the required conditional output filter level – it may take the same values as the message priority with the addition of MSG__NONE and MSG__ALL; by default it is set to MSG__NORM. The current conditional output filtering level may be inquired using subroutine msgIflev or compared against using msgFlevok. MSG__NONE and MSG__ALL allow every message to be hidden or all messages to be displayed respectively. These levels can not be used with msgOutif.

See also msgTune and msgIfgetenv that allows the filter level to be set by an environment variable, and msgIfget which allows ADAM programs to obtain the filter level from an ADAM parameter.

The action of msgOutif resulting from each of the defined priority values is as follows:

MSG__QUIET
– output the given messsage unless the current output filter is set to MSG__NONE;
MSG__NORM
– output the given message if the current output filter is set to either MSG__NORM, MSG__VERB, MSG__DEBUG (and related constants) or MSG__ALL;
MSG__VERB
– output the given message only if the current output filter is set to MSG__VERB, MSG__DEBUG (and related constants) or MSG__ALL.
MSG__DEBUG
– output the given message only if the current output filter is set to MSG__DEBUG (and related constants) or MSG__ALL.

In this scheme, messages given the priority MSG__QUIET the most important messages being output by an application and can only be turned off if the user is forcing all output to be disabled.

Here is an example of how conditional message output might be used in an application using interactive graphics with differing levels of informational messages to match how familiar the user is with the application:

*  Use the cursor to enter the approximate positions of stars on the
*  displayed image to be fitted.
CALL MSG_OUT( ’ ’, ’Use the cursor to enter star positions’,
:             STATUS )

*  Explain the positioning of the cursor.
CALL MSG_OUTIF( MSG__NORM, ’ ’,
:               ’The graphics cursor should be positioned ’ //
:               ’close to the centre of each star image’, STATUS )

*  Explain the cursor keys to new user.
CALL MSG_OUTIF( MSG__VERB, ’ ’, ’Cursor keys: 1 add entry’, STATUS )
CALL MSG_OUTIF( MSG__VERB, ’ ’, ’             2 reject last entry’,
:               STATUS )
CALL MSG_OUTIF( MSG__VERB, ’ ’, ’             3 entry complete’,
:               STATUS )

/*  Use the cursor to enter the approximate positions of stars on the
*  displayed image to be fitted. */
msgOut( "", "Use the cursor to enter star positions", status );

/*  Explain the positioning of the cursor. */
msgOutif( MSG__NORM, "",
"The graphics cursor should be positioned "
"close to the centre of each star image", status );

/*  Explain the cursor keys to new user. */
msgOutif( MSG__VERB, "", "Cursor keys: 1 add entry", status );
msgOutif( MSG__VERB, "", "             2 reject last entry", status );
msgOutif( MSG__VERB, "", "             3 entry complete", status );

The msgBlankif function works in much the same way as msgOutif to allow a blank line to be output conditionally.

msgBlankif( MSG__VERB, status );

CALL MSG_BLANKIF( MSG__VERB, STATUS )

The MSG__NONE and MSG__ALL levels cannot be used with msgBlankif.

2.4 Conditional Message Reporting and msgOut

Although conditional message reporting may be handled explicitly by calling msgOutif, calls to msgOut and msgBlank also have conditional message reporting built in. Their output has priority MSG__NORM associated with it and will therefore only be output when the conditional output filter is set to MSG__NORM (the default), MSG__VERB or greater.

2.5 Message tokens

In the previous examples of msgOut and msgOutif, the message text is “constant” in the sense that it does not refer to any variable items, e.g. file names or numeric values. However, very often, applications need to include the values of variables within output messages. This is done in the Message System using tokens embedded within the message text. For example, a program which measures the intensity of an emission line in a spectrum can output its result by:

msgSetr( "FLUX", flux );
msgOut( "EXAMPLE_RESULT",
"Emission flux is ^FLUX (erg/cm2/A/s).", status);

CALL MSG_SETR( ’FLUX’, FLUX )
CALL MSG_OUT( ’EXAMPLE_RESULT’,
:             ’Emission flux is ^FLUX (erg/cm2/A/s).’,
:             STATUS )

Here, the subroutine msgSetr is called to define a token named “FLUX” and assign to it the value of the REAL or float variable FLUX encoded as a character string. The token name, immediately preceded by the up-arrow, “^”, escape character is then included in the given message text. As the given text is processed, the token is expanded to the string assigned to the token.

For example, If the variable FLUX in this example has the value 2.4, then the message output to the terminal would be:

Emission flux is 2.4 (erg/cm2/A/s).

There is a set of msgSetx subroutines, one subroutine for each of five standard Fortran 77 data types (the Fortran type COMPLEX has not been provided for). Here, x corresponds to the Fortran data type of the value to be assigned to the named message token:

 x Fortran Type d DOUBLE PRECISION r REAL i INTEGER l LOGICAL c CHARACTER

In each case, the calling sequence is of the form:

msgSetx( token, value );

CALL MSG_SETx( TOKEN, VALUE )

where token is a character string giving the name chosen by the user and value is a variable or constant of the appropriate type. The numeric subroutines, msgSetd, msgSetr, and msgSeti, adopt the most concise format that will represent the value by removing trailing zeros, leading and trailing blanks, and by avoiding the use of exponential notation unless it is necessary. msgSetl uses TRUE or FALSE according to the value it is given. msgSetc removes trailing blanks from the character string; leading blanks are not removed.

An additional feature of the msgSetx routines is that calls to these routines using an existing token will result in the value being appended to the previously assigned token string. Here is the previous example written to exploit this feature of the msgSetx routines:

/* Local Constants: */
const char *funits = " erg/cm2/A/s";
...

msgSetr( "FLUX", flux );
msgSetc( "FLUX", funits );
msgOut( "EXAMPLE_RESULT",
"Emission flux is ^FLUX", status );

*  Local Constants:
CHARACTER FUNITS * 12
PARAMETER( FUNITS = ’ erg/cm2/A/s’ )

...

CALL MSG_SETR( ’FLUX’, FLUX )
CALL MSG_SETC( ’FLUX’, FUNITS )
CALL MSG_OUT( ’EXAMPLE_RESULT’,
:             ’Emission flux is ^FLUX.’, STATUS )

where the CHARACTER variable funits has been assigned the value of an appropriate unit of flux (e.g. erg/cm2/A/s) earlier in the program. Note that repeated calls to the msgSetx routines will append values to the token string with no separator, hence a leading space in the funits string is needed to separate the flux value and its units in the expanded message.

The text string associated with a message token (i.e. the token value) may be up to 200 characters long. Token names may be up to 15 characters long and should be valid names: i.e. they should begin with an alphabetic character and continue with alphanumeric or underscore characters. A maximum of 64 uniquely named message tokens may be included in any output message.

No message tokens are defined initially and after each call to msgOut, msgOutif or msgLoad (or a corresponding ERR routine, see §3.4) all existing tokens are left undefined.

2.6 Formatted tokens from C

The msgSetx functions encode numeric values in the most concise format that will describe the supplied value. Sometimes, however, a specific format is required; for example, the message may form part of a table. More precise control can be obtained by using the msgFmt function to provide all the standard sprintf() formats available from the standard C library.

msgFmt( token, format, ... );

and the previous example could be written as:

msgFmt( "FLUX", "%5.2f", flux );
msgSetc( "FLUX", funits );
msgOut( "EXAMPLE_RESULT",
"Emission flux is ^FLUX", status );

There is no restriction on the number of formats that can be included in a single call so the above example can be simplified further:

msgFmt( "FLUX", "%5.2f %s", flux, funits );
msgOut( "EXAMPLE_RESULT",
"Emission flux is ^FLUX", status );

2.7 Formatted tokens from Fortran

The Fortran MSG_SETx subroutines encode numeric values in the most concise format that will describe the supplied value. This is normally what is wanted for a simple message. The Fortran interface provides an API similar to that for the C library but using standard Fortran format conventions.

More precise numeric formats could be achieved using the Fortran WRITE statement with the Message System, e.g.

*  Local Constants:
CHARACTER * 12 FUNITS
PARAMETER( FUNITS = ’ erg/cm2/A/s’ )

*  Local Variables:
CHARACTER * 12 VALUE

...

*  Output the flux value.
WRITE( VALUE, ’( 1E12.5 )’ ) FLUX
CALL MSG_SETC( ’FLUX’, VALUE )
CALL MSG_SETC( ’FLUX’, FUNITS )
CALL MSG_OUT( ’EXAMPLE_RESULT’,
:             ’Emission flux is ^FLUX.’, STATUS )

which would produce the message:

Emission flux is  2.40000E+00 (erg/cm2/A/s).

In this case, the first call to MSG_SETC assigns the supplied character string VALUE to the named token “FLUX”.

Since this sequence of a formatted internal WRITE followed by a call to MSG_SETC is of general use, it is provided in a set of subroutines of the form:

CALL MSG_FMTx( TOKEN, FORMAT, VALUE )

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 one of the five standard Fortran data types – D, R, I, L and C.

Using MSG_FMTx, the example given above can be performed by:

*  Output the flux value.
CALL MSG_FMTR( ’FLUX’, ’1E12.5’, FLUX )
CALL MSG_SETC( ’FLUX’, FUNITS )
CALL MSG_OUT( ’EXAMPLE_RESULT’,
:             ’Emission flux is ^FLUX.’, STATUS )

The use of the MSG_FMTx routines along with their ability to append values of any type to existing tokens is a very powerful tool for constructing tabular output from applications software.

2.8 Direct formatted message output with msgOut

The C interface provides variants to the standard msgOut and msgOutif functions that can handle sprintf() style format strings. This can lead to a significant reduction in the number of lines spent setting up tokens by embedding the formatting directly in the required string. The example from the previous section can simply be written as:

msgOutf( "EXAMPLE_RESULT",
"Emission flux is %5.2f %s", status, flux, funits );

msgOutf and msgOutiff act like printf() whereas msgOutifv is a variant of msgOutif that takes a va_list argument rather than variadic ”$\dots$” arguments. The “%” character is special in this context and the ADAM definition (See Appendix E.2.2) is not available. A literal “%” is obtained by doubling up the symbol (“%%”) as would be used in printf() and described in the next section.

2.9 Including escape characters in messages

Sometimes it is necessary to include the message token escape character, “^”, literally in a message. When the message token escape character is immediately followed by a blank space, or is at the end of the msgOut text, it is included literally. If this is not the case, then it can be included literally by duplicating it. So, for example:

msgSetc( "TOKEN", "message token" );
msgOut( "EXAM_UPARROW",
"Up-arrow, ^^, is the ^TOKEN escape character.", status);

CALL MSG_SETC( ’TOKEN’, ’message token’ )
CALL MSG_OUT( ’EXAM_UPARROW’,
:             ’Up-arrow, ^^, is the ^TOKEN escape character.’,
:             STATUS )

would produce the message:

Up-arrow, ^, is the message token escape character.

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. This means that if the message system is to be used to output the value of a character variable, the contents of which are unknown and may therefore include escape characters, the value should first be assigned to a message token. Thus,

msgSetc( "TEXT", value );
msgOut( "EXAMPLE_OK", "^TEXT", status );

CALL MSG_SETC( ’TEXT’, VALUE )
CALL MSG_OUT( ’EXAMPLE_OK’, ’^TEXT’, STATUS )

will output the contents of VALUE literally, whereas

CALL MSG_OUT( ’EXAMPLE_BAD’, VALUE, STATUS )

might not produce the desired result. This consideration is particularly important when outputting text values such as file names within the ADAM environment, where a number of additional escape characters are defined (see Appendix E.2).

2.10 Intercepting messages

It may sometimes be convenient within an application to write the text of a message, complete with decoded message tokens, to a character variable instead of the standard output stream. The Message System provides subroutine msgLoad to do this. msgLoad has the calling sequence:

msgLoad( param, text, opstr, opstr_len, oplen, status );

CALL MSG_LOAD( PARAM, TEXT, OPSTR, OPLEN, STATUS )

Here, the arguments param, text and status are identical to those for msgOut. The behaviour of msgLoad is also the same as msgOut except that, instead of sending the expanded message text to the standard output stream, msgLoad returns it in the character variable optstr (regardless the output filtering level). oplen returns the length of the message in opstr. If the message text is longer than the declared length of opstr (as specified in opstr_len in the C interface), then the message is truncated with an ellipsis, i.e. “…”, but no error results.

The symbolic constant MSG__SZMSG is provided for defining the length of character variables which are to hold such messages. This constant is defined in the include files msg_par.h and MSG_PAR (see §6).

2.11 Renewing annulled message tokens

Each call to msgOut, msgOutif or msgLoad will annul any defined message tokens, regardless of the success of the call. This feature of the Message Reporting System ensures that message token names can be re-used with safety in a series of calls to, say, msgOut (e.g. in order to output a table of values line by line). However, under certain circumstances it is useful to be able to restore, or renew, the values of any message tokens set prior to the call to the output routine. This can be done using the subroutine msgRenew. In order to be effective, msgRenew must be called after the call which annulled the required message tokens and prior to any further message token definitions (e.g. using the msgSetx and MSG_FMTx routines). If msgRenew is called with existing message tokens defined, no action is taken.

Here is a Fortran example of the use of msgRenew where a table of values is being output to the user and to a log file:

*  Loop to output the table.
DO 10 I = 1, NROWS

*     Set a token for each column.
CALL MSG_FMTI( ’COL1’, ’1X, I10’, I )
CALL MSG_FMTI( ’COL2’, ’I7’, HDNUMB( I ) )
CALL MSG_FMTR( ’COL3’, ’F10.5’, X( I ) )
CALL MSG_FMTR( ’COL4’, ’F10.5’, Y( I ) )
CALL MSG_FMTR( ’COL5’, ’F10.5’, Z( I ) )

*     Output a row of the table to the user.
CALL MSG_OUT( ’ ’, ’^COL1 ^COL2 ^COL3 ^COL4 ^COL5’, STATUS )

*     Renew the token values.
CALL MSG_RENEW

*     Build a string with the row of data.
CALL MSG_LOAD( ’ ’, ’^COL1 ^COL2 ^COL3 ^COL4 ^COL5’, OPSTR,
:                 OPLEN, STATUS )

*     Write the string to a file.
WRITE( OPFILE, ’( 1X, A )’, IOSTAT = IOSTAT ) OPSTR( 1 : OPLEN )
10   CONTINUE

2.12 Resilience

Other than msgOut, msgOutif, msgBlank, msgBlankif and msgLoad, the Message System subroutines do not use a status argument. This is because they are intended to be very robust. In order to construct a message for output to the user, they will attempt to recover from any internal failure. The STATUS argument in the “output” routines conforms to the Starlink convention for inherited status (see §3.2). This means that an application can contain sequences of msgSetx, MSG_FMTx and msgOut calls, and only needs to check the status at the end.

There are two kinds of “failure” that can occur within the Message System:

• Message Construction – Any tokens which cannot be evaluated while constructing the output message are indicated in the message text by using a special syntax. This syntax is illustrated for the text resulting from the call:

msgOut( "EXAMPLE_FLUX",  "Emission flux is ^FLUX (erg/cm2/s).",
status );

CALL MSG_OUT( ’EXAMPLE_FLUX’,
:             ’Emission flux is ^FLUX (erg/cm2/s).’, STATUS )

If the token “FLUX” is not defined, then this call will produce the text:

Emission flux is ^<FLUX> (erg/cm2/s).

There are several reasons why a token may be undefined:

• It has not been defined using msgSetx etc.
• It has been annulled by a previous call to msgOut, msgOutif or msgLoad (or a corresponding ERR routine).
• An attempt has been made to define more than 64 message tokens.
• An error has been made in a call to one of the MSG_FMTx subroutines. This error could be either a syntax error in the FORMAT argument, or the result of specifying a field width which is too small (referred to as an “output conversion error” in Fortran).

Errors in message construction which result from undefined tokens are not considered fatal and STATUS is not set as a result.

• Message Output – If an error occurs when msgOut, msgOutif, msgBlank or msgBlankif attempt to output the message, then the STATUS argument will be set to an error value and an error message will be reported (see §3.4). Errors writing the message text to the standard output stream may occur for a number of reasons – the seriousness of such an error is dependent upon where the message was intended to go:
• Command terminal or VAX/VMS batch log file – In this case the likelihood of being able to inform the user about the error is small. Generally, the application has little chance of continuing successfully under these circumstances.
• Output file – The message output may have been re-directed to an output file for the duration of a particular application. Under these circumstances, the user can probably be informed about the error and the application may be able to continue.