Modernising old libraries - error handling

For new libraries, I see three approaches

  1. The standard approach of having an (optional) integer stat argument; optionally with a deferred-length character for an error message. In case the user does not query the stat argument the routine will fail gracefully (issuing a message). The library should uphold to some conventions, like using negative values for errors, 0 for success, and positive values for warnings or other possible outcomes. The library should also offer integer parameters, that can be used to query for specific errors.
  2. A status variable encapsulated in a derived type, which is also the entry point for the library (e.g. an integrator object); the derived type should carry routines to query the status flag and provide a message.
  3. A more modern approach would be to use a dedicated library such as ErrorFx: Fortran exception library. While more powerful, it also puts burden on the consumer since they have to learn not only how to use the error library, but also how to build it and link it with their application.

A few more guidelines which I find relevant for programs that might produce command-line output (taken from https://clig.dev):

  • Return zero exit code on success, non-zero on failure. Exit codes are how scripts determine whether a program succeeded or failed, so you should report this correctly. Map the non-zero exit codes to the most important failure modes.

  • Send output to stdout . The primary output for your command should go to stdout . Anything that is machine readable should also go to stdout —this is where piping sends things by default.

  • Send messaging to stderr . Log messages, errors, and so on should all be sent to stderr . This means that when commands are piped together, these messages are displayed to the user and not fed into the next command.

For logging, the stdlib logger_type can be used. One downside of adding a (local) logger is it requires the library to offer an initialization routine that is called by the consumer in the main program. Alternatively, the logger needs to be initialized in the first call

subroutine library_entry_point( ... )
  use library_internals: library_logger
  logical, save :: first_call = .true.

  if (first_call) then
    call library_logger % add_log_unit( ... )
    first_call = .false.
  end if

  ! ... actual library stuff ...

end subroutine

Some extra care is needed to make sure the logger calls are thread-safe.

7 Likes