logger.f90 Source File


Source Code

!> @file logger.f90
!> @brief Singleton logger with configurable verbosity and dual output targets.
!!
!! Provides five log levels (SILENT, ERROR, WARN, INFO, DEBUG) and routes
!! messages to stdout, an optional log file, or both.  The module is safe to
!! use before `log_init` is called: it defaults to INFO level with no file.
!!
!! Level constants (use these in namelist `verbosity` field):
!!   LOGLVL_SILENT = 0 — suppress all output
!!   LOGLVL_ERROR  = 1 — errors only
!!   LOGLVL_WARN   = 2 — warnings and errors
!!   LOGLVL_INFO   = 3 — normal operational messages (default)
!!   LOGLVL_DEBUG  = 4 — developer diagnostics
!!
!! Typical usage in the driver:
!! @code
!!   call log_init(cfg % verbosity, cfg % log_file)
!!   call log_info('config: loaded "input.nml"')
!!   call log_warn('reconstruction: char_proj ignored on FDS path')
!!   call log_finalize()
!! @endcode

module logger
  use iso_fortran_env, only: output_unit
  implicit none
  private

  !> Log level constants.  Higher value = more verbose.
  !! Note: names use LOGLVL_ prefix to avoid case-insensitive clash with
  !! the log_error / log_warn / log_info / log_debug subroutines.
  integer, parameter, public :: LOGLVL_SILENT = 0 !< Suppress all output
  integer, parameter, public :: LOGLVL_ERROR = 1 !< Fatal / unrecoverable errors
  integer, parameter, public :: LOGLVL_WARN = 2 !< Non-fatal warnings
  integer, parameter, public :: LOGLVL_INFO = 3 !< Normal operational messages
  integer, parameter, public :: LOGLVL_DEBUG = 4 !< Developer diagnostics

  ! Singleton state — safe to call log_* before log_init (defaults to INFO, no file).
  integer :: log_verbosity = LOGLVL_INFO
  integer :: log_file_unit = -1
  logical :: log_file_open = .false.

  public :: log_init, log_finalize
  public :: log_error, log_warn, log_info, log_debug

contains

  !> Initialise the logger: set verbosity level and optionally open a log file.
  !!
  !! @param[in] verbosity  Threshold level (LOGLVL_SILENT … LOGLVL_DEBUG).
  !! @param[in] log_file   Path for the log file.  Empty string disables file logging.
  subroutine log_init(verbosity, log_file)
    integer, intent(in) :: verbosity
    character(len=*), intent(in) :: log_file
    integer :: stat

    log_verbosity = verbosity

    if (len_trim(log_file) > 0) then
      open (newunit=log_file_unit, file=trim(log_file), status='replace', &
            action='write', iostat=stat)
      if (stat /= 0) then
        write (output_unit, '(A,A,A)') '[WARN]  logger: could not open log file "', &
          trim(log_file), '" — file logging disabled'
        log_file_open = .false.
      else
        log_file_open = .true.
      end if
    end if
  end subroutine log_init

  !> Close the log file and reset file-logging state.
  subroutine log_finalize()
    integer :: stat
    if (log_file_open) then
      close (log_file_unit, iostat=stat)
      log_file_open = .false.
      log_file_unit = -1
    end if
  end subroutine log_finalize

  !> Emit an ERROR-level message.
  subroutine log_error(msg)
    character(len=*), intent(in) :: msg
    call emit('[ERROR] ', msg, LOGLVL_ERROR)
  end subroutine log_error

  !> Emit a WARN-level message.
  subroutine log_warn(msg)
    character(len=*), intent(in) :: msg
    call emit('[WARN]  ', msg, LOGLVL_WARN)
  end subroutine log_warn

  !> Emit an INFO-level message.
  subroutine log_info(msg)
    character(len=*), intent(in) :: msg
    call emit('[INFO]  ', msg, LOGLVL_INFO)
  end subroutine log_info

  !> Emit a DEBUG-level message.
  subroutine log_debug(msg)
    character(len=*), intent(in) :: msg
    call emit('[DEBUG] ', msg, LOGLVL_DEBUG)
  end subroutine log_debug

  ! ---------------------------------------------------------------------------
  ! Private helper — performs the actual write to stdout and/or the log file.
  ! ---------------------------------------------------------------------------
  subroutine emit(tag, msg, threshold)
    character(len=*), intent(in) :: tag, msg
    integer, intent(in) :: threshold
    integer :: stat

    if (log_verbosity < threshold) return

    write (output_unit, '(A,A)', iostat=stat) tag, trim(msg)
    if (log_file_open) &
      write (log_file_unit, '(A,A)', iostat=stat) tag, trim(msg)
  end subroutine emit

end module logger