!> @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