logger.f90 Source File


This file depends on

sourcefile~~logger.f90~~EfferentGraph sourcefile~logger.f90 logger.f90 sourcefile~kinds.f90 kinds.f90 sourcefile~logger.f90->sourcefile~kinds.f90

Files dependent on this one

sourcefile~~logger.f90~~AfferentGraph sourcefile~logger.f90 logger.f90 sourcefile~datastructs_mod.f90 datastructs_mod.f90 sourcefile~datastructs_mod.f90->sourcefile~logger.f90

Source Code

!> Logging module for managing verbosity levels and output destinations.
!> This module provides a simple logging system with support for different log
!> levels (ERROR, WARNING, INFO, DEBUG), configurable verbosity, and output units.
!>
!> Features:
!> - Enable or disable global verbosity
!> - Control the log level threshold (e.g., show only ERROR, or include DEBUG)
!> - Redirect logs to custom output units or reset to defaults (stdout/stderr)
!> - Helper function to determine whether to log and which unit to use
!> - Convenience subroutine to write logs directly
!>
!> @note
!> This module is designed for modular use and can be applied to any program.
!> @endnote
module datastructs_logger_mod
    use datastructs_kinds_mod
    use iso_fortran_env, only: output_unit, error_unit
    implicit none
    private

    ! Log level constants
    integer, parameter :: LOG_ERROR   = 0   !! Error messages
    integer, parameter :: LOG_WARNING = 1   !! Warning messages
    integer, parameter :: LOG_INFO    = 2   !! Informational messages
    integer, parameter :: LOG_DEBUG   = 3   !! Debug messages

    character(len=*), parameter :: names(0:3) = ['[error]  ', '[warning]', '[info]   ', '[debug]  ']

    !> Global verbosity flag (if .false., no log messages are printed)
    logical :: LOGGER_VERBOSE = .true.
    !> Current log level threshold (default: LOG_INFO)
    integer(kind=i4) :: LOGGER_LEVEL = LOG_INFO
    !> Output unit for normal logs (default: standard output)
    integer(kind=i4) :: LOGGER_OUTPUT_UNIT = output_unit
    !> Output unit for error logs (default: standard error)
    integer(kind=i4) :: LOGGER_ERROR_UNIT = error_unit
    !> Indicates if the last call to log_unit allowed logging
    logical :: LOGGER_OK = .true.

    interface log_write
        module procedure log_write_message
        module procedure log_write_message_no_advance
        module procedure log_write_message_i4
        module procedure log_write_message_i4_no_advance
        module procedure log_write_message_dp
        module procedure log_write_message_dp_no_advance
    end interface log_write

    public :: log_unit, set_verbose, set_level, set_output_unit, set_error_unit, set_unit_defaults, log_write
    public :: LOG_ERROR, LOG_WARNING, LOG_INFO, LOG_DEBUG, LOGGER_OK

contains
    !> Enable or disable global verbosity.
    !> If set to `.false.`, no log messages will be printed regardless of level.
    subroutine set_verbose(flag)
        logical, intent(in) :: flag !! Logical flag to enable (`.true.`) or disable (`.false.`) verbosity.
        LOGGER_VERBOSE = flag
    end subroutine set_verbose

    !> Set the current log level threshold.
    !! Only messages with level <= LOGGER_LEVEL will be printed.
    subroutine set_level(level)
        integer(kind=i4), intent(in) :: level !! Log level (use LOG_ERROR, LOG_WARNING, LOG_INFO, LOG_DEBUG)
        LOGGER_LEVEL = level
    end subroutine set_level

    !> Set the output unit for normal log messages.
    subroutine set_output_unit(unit)
        integer(kind=i4), intent(in) :: unit !! Fortran unit number for INFO/WARNING/DEBUG logs.
        LOGGER_OUTPUT_UNIT = unit
    end subroutine set_output_unit

    !> Set the output unit for error log messages.
    subroutine set_error_unit(unit)
        integer(kind=i4), intent(in) :: unit !! Fortran unit number for ERROR logs.
        LOGGER_ERROR_UNIT = unit
    end subroutine set_error_unit

    !> Reset output units to default (stdout for normal logs, stderr for errors).
    subroutine set_unit_defaults()
        LOGGER_OUTPUT_UNIT = output_unit
        LOGGER_ERROR_UNIT = error_unit
    end subroutine set_unit_defaults

    !> Determine the output unit for a given log level and update LOGGER_OK.
    !>
    !> This function returns the Fortran unit where the message should be written,
    !> and sets LOGGER_OK to indicate if the log should be written.
    function log_unit(level) result(unit)
        integer(kind=i4), intent(in) :: level !! Log level of the message
        integer(kind=i4) :: unit !! The Fortran output unit (or -1 if logging is disabled)

        unit = -1
        LOGGER_OK = .false.

        if (.not. LOGGER_VERBOSE) return

        select case(level)
          case(LOG_ERROR)
            unit = LOGGER_ERROR_UNIT
            LOGGER_OK = .true.
          case(LOG_WARNING)
            if (LOGGER_LEVEL >= LOG_WARNING) then
                unit = LOGGER_OUTPUT_UNIT
                LOGGER_OK = .true.
            end if
          case(LOG_INFO)
            if (LOGGER_LEVEL >= LOG_INFO) then
                unit = LOGGER_OUTPUT_UNIT
                LOGGER_OK = .true.
            end if
          case(LOG_DEBUG)
            if (LOGGER_LEVEL >= LOG_DEBUG) then
                unit = LOGGER_OUTPUT_UNIT
                LOGGER_OK = .true.
            end if
        end select
    end function log_unit

    !> Write a log message for the given level using the current logger settings.
    !>
    !> This is a convenience routine that calls log_unit() internally and writes
    !> the message if LOGGER_OK is `.true.`.
    subroutine log_write_message(level, message)
        integer(kind=i4), intent(in) :: level !! Log level of the message
        character(len=*), intent(in) :: message !! The text message to log
        integer(kind=i4) :: unit

        unit = log_unit(level)
        if (LOGGER_OK) write(unit, fmt_general) names(level), message
    end subroutine log_write_message

    !> Write a log message for a given level without advancing the line
    subroutine log_write_message_no_advance(level, message, newline)
        integer(kind=i4), intent(in) :: level !! Log level of the message
        character(len=*), intent(in) :: message !! The text message to log
        logical, intent(in) :: newline !! If `.false.`, do not append a newline
        character(len=:), allocatable :: advance
        integer(kind=i4) :: unit

        advance = "no"
        if (newline) advance = "yes"

        unit = log_unit(level)
        if (LOGGER_OK) write(unit, fmt_general, advance=advance) names(level), message
    end subroutine log_write_message_no_advance

    !> Write a log message for a given level, appending an integer at the end
    subroutine log_write_message_i4(level, message, value)
        integer(kind=i4), intent(in) :: level !! Log level of the message
        character(len=*), intent(in) :: message !! The text message to log
        integer(kind=i4), intent(in) :: value !! Integer value to append
        integer(kind=i4) :: unit

        unit = log_unit(level)
        if (LOGGER_OK) write(unit, fmt_general) names(level), message, value
    end subroutine log_write_message_i4

    !> Write a log message for a given level without advancing the line, appending an integer at the end
    subroutine log_write_message_i4_no_advance(level, message, value, newline)
        integer(kind=i4), intent(in) :: level !! Log level of the message
        character(len=*), intent(in) :: message !! The text message to log
        integer(kind=i4), intent(in) :: value !! Integer value to append
        logical, intent(in) :: newline !! If `.false.`, do not append a newline
        integer(kind=i4) :: unit
        character(len=:), allocatable :: advance

        advance = "no"
        if (newline) advance = "yes"

        unit = log_unit(level)
        if (LOGGER_OK) write(unit, fmt_general, advance=advance) names(level), message, value
    end subroutine log_write_message_i4_no_advance

    !> Write a log message for a given level, appending a double at the end
    subroutine log_write_message_dp(level, message, value)
        integer(kind=i4), intent(in) :: level !! Log level of the message
        character(len=*), intent(in) :: message !! The text message to log
        real(kind=dp), intent(in) :: value !! Double precision value to append
        integer(kind=i4) :: unit

        unit = log_unit(level)
        if (LOGGER_OK) write(unit, fmt_general) names(level), message, value
    end subroutine log_write_message_dp

    !> Write a log message for a given level without advancing the line, appending a double at the end
    subroutine log_write_message_dp_no_advance(level, message, value, newline)
        integer(kind=i4), intent(in) :: level !! Log level of the message
        character(len=*), intent(in) :: message !! The text message to log
        real(kind=dp), intent(in) :: value !! Double precision value to append
        logical, intent(in) :: newline !! If `.false.`, do not append a newline
        integer(kind=i4) :: unit
        character(len=:), allocatable :: advance

        advance = "no"
        if (newline) advance = "yes"

        unit = log_unit(level)
        if (LOGGER_OK) write(unit, fmt_general, advance=advance) names(level), message, value
    end subroutine log_write_message_dp_no_advance

end module datastructs_logger_mod