!--------------------------------------------------------------------------------------------------!
!   CP2K: A general program to perform molecular dynamics simulations                              !
!   Copyright 2000-2025 CP2K developers group <https://cp2k.org>                                   !
!                                                                                                  !
!   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
!--------------------------------------------------------------------------------------------------!

! **************************************************************************************************
!> \brief various routines to log and control the output.
!>      The idea is that decisions about where to log should not be done in
!>      the code that generates the log, but should be globally changeable
!>      a central place.
!>      So some care has been taken to have enough information about the
!>      place from where the log comes so that in the future intelligent and
!>      flexible decisions can be taken by the logger, without having to change
!>      other code.
!> \note
!>      contains also routines to convert to a string.
!>      in my idea they should have been with variable length,
!>      (i.e. they should have returned a trim(adjustl(actual_result)))
!>      As a logger should be robust, at the moment I have given up.
!>
!>      At the moment logging and output refer to the same object
!>      (cp_logger_type)
!>      as these are actually different it might be better to separate them
!>      (they have already separate routines in a separate module
!>      @see cp_output_handling).
!>
!>      some practices (use of print *, no cp_error_type,
!>      manual retain release of some objects) are dictated by the need to
!>      have minimal dependency
!> \par History
!>      08.2002 major update: retain, release, printkeys, para_env,
!>              local logging [fawzi]
!>      02.2004 made a stack of default loggers [Joost VandeVondele]
!> \par
!>      @see cp_error_handling
!> \author Fawzi Mohamed
!>      @version 12.2001
! **************************************************************************************************
MODULE cp_log_handling
   USE cp_files,                        ONLY: close_file,&
                                              open_file
   USE cp_iter_types,                   ONLY: cp_iteration_info_create,&
                                              cp_iteration_info_release,&
                                              cp_iteration_info_retain,&
                                              cp_iteration_info_type
   USE kinds,                           ONLY: default_path_length,&
                                              default_string_length,&
                                              dp
   USE machine,                         ONLY: default_output_unit,&
                                              m_getpid,&
                                              m_hostnm
   USE message_passing,                 ONLY: mp_para_env_release,&
                                              mp_para_env_type
   USE string_utilities,                ONLY: compress
   USE timings,                         ONLY: print_stack
#include "../base/base_uses.f90"

   IMPLICIT NONE
   PRIVATE

   !API types
   PUBLIC :: cp_logger_type, cp_logger_p_type
   !API parameter vars
   PUBLIC :: cp_note_level, cp_warning_level, cp_failure_level, cp_fatal_level
   !API default loggers
   PUBLIC :: cp_get_default_logger, cp_add_default_logger, cp_rm_default_logger, &
             cp_default_logger_stack_size
   !API logger routines
   PUBLIC :: cp_logger_create, cp_logger_retain, cp_logger_release, &
             cp_logger_would_log, cp_logger_set, cp_logger_get_default_unit_nr, &
             cp_logger_get_default_io_unit, cp_logger_get_unit_nr, &
             cp_logger_set_log_level, cp_logger_generate_filename, &
             cp_to_string

   CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'cp_log_handling'
   LOGICAL, PRIVATE, PARAMETER :: debug_this_module = .FALSE.

   !! level of an error
   INTEGER, PARAMETER          :: cp_fatal_level = 3
   !! level of a failure
   INTEGER, PARAMETER          :: cp_failure_level = 2
   !! level of a warning
   INTEGER, PARAMETER          :: cp_warning_level = 1
   !! level of a note
   INTEGER, PARAMETER          :: cp_note_level = 0

   !! a generic function to transform different types to strings
   INTERFACE cp_to_string
      MODULE PROCEDURE cp_int_to_string, cp_real_dp_to_string, cp_logical_to_string
   END INTERFACE

! **************************************************************************************************
!> \brief type of a logger, at the moment it contains just a print level
!>      starting at which level it should be logged
!>      (0 note, 1 warning, 2 failure, 3 fatal)
!>      it could be expanded with the ability to focus on one or more
!>      module/object/thread/processor
!> \param ref_count reference count (see cp2k/doc/ReferenceCounting.html)
!> \param print_level the level starting at which something gets printed
!> \param default_local_unit_nr default unit for local logging (-1 if not
!>        yet initialized). Local logging guarantee to each task its own
!>        file.
!> \param default_global_unit_nr default unit for global logging
!>        (-1 if not yet initialized). This unit is valid only on the
!>        processor with %para_env%mepos==%para_env%source.
!> \param para_env the parallel environment for the output.
!>        this might be a super environment of your computation environment
!>        i.e. be very careful not to do global operations like broadcast
!>        with a subset of its processors (use your computation environment
!>        instead).
!> \param close_local_unit_on_dealloc if the local unit should be closed
!>        when this logger is deallocated
!> \param close_global_unit_on_dealloc whether the global unit should be
!>        closed when this logger is deallocated
!> \param suffix a short string that is used as suffix in all the filenames
!>        created by this logger. Can be used to guarantee the unicity of
!>        generated filename
!> \param local_filename the root of the name of the file used for local
!>        logging (can be different from the name of the file corresponding
!>        to default_local_unit_nr, only the one used if the unit needs to
!>        be opened)
!> \param global_filename the root of the name of the file used for
!>        global logging (can be different from the name of the file
!>        corresponding to default_global_unit_nr, only the one used if
!>        the unit needs to be opened)
!> \param print_keys print keys that tell what should be logged/outputted
!> \note
!>      This should be private, but as the output functions have been
!>      moved to another module and there is no "friend" keyword, they
!>      are public.
!>      DO NOT USE THE INTERNAL COMPONENTS DIRECTLY!!!
!> \par History
!>      04.2002 revised [fawzi]
!>      08.2002 major update: retain, release, printkeys, para_env,
!>              local logging [fawzi]
!> \author Fawzi Mohamed
! **************************************************************************************************
   TYPE cp_logger_type
      INTEGER :: ref_count = -1
      INTEGER :: print_level = -1
      INTEGER :: default_local_unit_nr = -1
      INTEGER :: default_global_unit_nr = -1
      LOGICAL :: close_local_unit_on_dealloc = .FALSE., close_global_unit_on_dealloc = .FALSE.
      CHARACTER(len=default_string_length)  :: suffix = ""
      CHARACTER(len=default_path_length)    :: local_filename = "", global_filename = ""
      TYPE(mp_para_env_type), POINTER       :: para_env => NULL()
      TYPE(cp_iteration_info_type), POINTER :: iter_info => NULL()
   END TYPE cp_logger_type

   TYPE cp_logger_p_type
      TYPE(cp_logger_type), POINTER :: p => Null()
   END TYPE cp_logger_p_type

! **************************************************************************************************
   TYPE default_logger_stack_type
      TYPE(cp_logger_type), POINTER :: cp_default_logger => Null()
   END TYPE default_logger_stack_type

   INTEGER, PRIVATE            :: stack_pointer = 0
   INTEGER, PARAMETER, PRIVATE :: max_stack_pointer = 10
   TYPE(default_logger_stack_type), SAVE, DIMENSION(max_stack_pointer) ::  default_logger_stack

CONTAINS

! **************************************************************************************************
!> \brief ...
!> \return ...
!> \author fawzi
! **************************************************************************************************
   FUNCTION cp_default_logger_stack_size() RESULT(res)
      INTEGER                                            :: res

      res = stack_pointer
   END FUNCTION cp_default_logger_stack_size

! **************************************************************************************************
!> \brief adds a default logger.
!>      MUST be called before logging occours
!> \param logger ...
!> \author Fawzi Mohamed
!> \note
!>      increments a stack of default loggers the latest one will be
!>      available within the program
! **************************************************************************************************
   SUBROUTINE cp_add_default_logger(logger)
      TYPE(cp_logger_type), INTENT(INOUT), TARGET        :: logger

      CHARACTER(len=*), PARAMETER :: routineN = 'cp_add_default_logger', &
         routineP = moduleN//':'//routineN

      IF (stack_pointer + 1 > max_stack_pointer) THEN
         CALL cp_abort(__LOCATION__, routineP// &
                       "too many default loggers, increase max_stack_pointer in "//moduleN)
      END IF

      stack_pointer = stack_pointer + 1
      NULLIFY (default_logger_stack(stack_pointer)%cp_default_logger)

      default_logger_stack(stack_pointer)%cp_default_logger => logger
      CALL cp_logger_retain(logger)

   END SUBROUTINE cp_add_default_logger

! **************************************************************************************************
!> \brief the cousin of cp_add_default_logger, decrements the stack, so that
!>      the default logger is what it has
!>      been
!> \author Joost VandeVondele
! **************************************************************************************************
   SUBROUTINE cp_rm_default_logger()
      IF (stack_pointer - 1 < 0) THEN
         CALL cp_abort(__LOCATION__, moduleN//":cp_rm_default_logger "// &
                       "can not destroy default logger "//moduleN)
      END IF

      CALL cp_logger_release(default_logger_stack(stack_pointer)%cp_default_logger)
      NULLIFY (default_logger_stack(stack_pointer)%cp_default_logger)
      stack_pointer = stack_pointer - 1

   END SUBROUTINE cp_rm_default_logger

! **************************************************************************************************
!> \brief returns the default logger
!> \return ...
!> \par History
!>      4.2002 created [fawzi]
!> \author Fawzi Mohamed
!> \note
!>      initializes the default loggers if necessary
! **************************************************************************************************
   FUNCTION cp_get_default_logger() RESULT(res)
      TYPE(cp_logger_type), POINTER                      :: res

      IF (.NOT. stack_pointer > 0) THEN
         CALL cp_abort(__LOCATION__, "cp_log_handling:cp_get_default_logger "// &
                       "default logger not yet initialized (CALL cp_init_default_logger)")
      END IF
      res => default_logger_stack(stack_pointer)%cp_default_logger
      IF (.NOT. ASSOCIATED(res)) THEN
         CALL cp_abort(__LOCATION__, "cp_log_handling:cp_get_default_logger "// &
                       "default logger is null (released too much ?)")
      END IF
   END FUNCTION cp_get_default_logger

! ================== log ==================

! **************************************************************************************************
!> \brief initializes a logger
!> \param logger the logger to initialize
!> \param para_env the parallel environment (this is most likely the global
!>        parallel environment
!> \param print_level the level starting with which something is written
!>        (defaults to cp_note_level)
!> \param default_global_unit_nr the default unit_nr for output
!>        (if not given, and no file is given defaults to the standard output)
!> \param default_local_unit_nr the default unit number for local (i.e. task)
!>        output. If not given defaults to a out.taskid file created upon
!> \param global_filename a new file to open (can be given instread of the
!>        global_unit_nr)
!> \param local_filename a new file to open (with suffix and para_env%mepos
!>        appended). Can be given instread of the default_local_unit_nr).
!>        the file is created only upon the first local logging request
!> \param close_global_unit_on_dealloc if the unit should be closed when the
!>        logger is deallocated (defaults to true if a local_filename is given,
!>        to false otherwise)
!> \param iter_info ...
!> \param close_local_unit_on_dealloc if the unit should be closed when the
!>        logger is deallocated (defaults to true)
!> \param suffix the suffix that should be added to all the generated filenames
!> \param template_logger a logger from where to take the unspecified things
!> \par History
!>      4.2002 created [fawzi]
!> \author Fawzi Mohamed
!> \note
!>      the handling of *_filename, default_*_unit_nr, close_*_unit_on_dealloc
!>      tries to take the right decision with different inputs, and thus is a
!>      little complex.
! **************************************************************************************************
   SUBROUTINE cp_logger_create(logger, para_env, print_level, &
                               default_global_unit_nr, default_local_unit_nr, global_filename, &
                               local_filename, close_global_unit_on_dealloc, iter_info, &
                               close_local_unit_on_dealloc, suffix, template_logger)
      TYPE(cp_logger_type), POINTER                      :: logger
      TYPE(mp_para_env_type), OPTIONAL, POINTER          :: para_env
      INTEGER, INTENT(in), OPTIONAL                      :: print_level, default_global_unit_nr, &
                                                            default_local_unit_nr
      CHARACTER(len=*), INTENT(in), OPTIONAL             :: global_filename, local_filename
      LOGICAL, INTENT(in), OPTIONAL                      :: close_global_unit_on_dealloc
      TYPE(cp_iteration_info_type), OPTIONAL, POINTER    :: iter_info
      LOGICAL, INTENT(in), OPTIONAL                      :: close_local_unit_on_dealloc
      CHARACTER(len=*), INTENT(in), OPTIONAL             :: suffix
      TYPE(cp_logger_type), OPTIONAL, POINTER            :: template_logger

      CHARACTER(len=*), PARAMETER :: routineN = 'cp_logger_create', &
         routineP = moduleN//':'//routineN

      ALLOCATE (logger)

      NULLIFY (logger%para_env)
      NULLIFY (logger%iter_info)
      logger%ref_count = 1

      IF (PRESENT(template_logger)) THEN
         IF (template_logger%ref_count < 1) &
            CPABORT(routineP//" template_logger%ref_count<1")
         logger%print_level = template_logger%print_level
         logger%default_global_unit_nr = template_logger%default_global_unit_nr
         logger%close_local_unit_on_dealloc = template_logger%close_local_unit_on_dealloc
         IF (logger%close_local_unit_on_dealloc) THEN
            logger%default_local_unit_nr = -1
         ELSE
            logger%default_local_unit_nr = template_logger%default_local_unit_nr
         END IF
         logger%close_global_unit_on_dealloc = template_logger%close_global_unit_on_dealloc
         IF (logger%close_global_unit_on_dealloc) THEN
            logger%default_global_unit_nr = -1
         ELSE
            logger%default_global_unit_nr = template_logger%default_global_unit_nr
         END IF
         logger%local_filename = template_logger%local_filename
         logger%global_filename = template_logger%global_filename
         logger%para_env => template_logger%para_env
         logger%suffix = template_logger%suffix
         logger%iter_info => template_logger%iter_info
      ELSE
         ! create a file if nothing is specified, one can also get the unit from the default logger
         ! which should have something reasonable as the argument is required in that case
         logger%default_global_unit_nr = -1
         logger%close_global_unit_on_dealloc = .TRUE.
         logger%local_filename = "localLog"
         logger%global_filename = "mainLog"
         logger%print_level = cp_note_level
         ! generate a file for default local logger
         ! except the ionode that should write to the default global logger
         logger%default_local_unit_nr = -1
         logger%close_local_unit_on_dealloc = .TRUE.
         logger%suffix = ""
      END IF
      IF (PRESENT(para_env)) logger%para_env => para_env
      IF (.NOT. ASSOCIATED(logger%para_env)) &
         CPABORT(routineP//" para env not associated")
      IF (.NOT. logger%para_env%is_valid()) &
         CPABORT(routineP//" para_env%ref_count<1")
      CALL logger%para_env%retain()

      IF (PRESENT(print_level)) logger%print_level = print_level

      IF (PRESENT(default_global_unit_nr)) &
         logger%default_global_unit_nr = default_global_unit_nr
      IF (PRESENT(global_filename)) THEN
         logger%global_filename = global_filename
         logger%close_global_unit_on_dealloc = .TRUE.
         logger%default_global_unit_nr = -1
      END IF
      IF (PRESENT(close_global_unit_on_dealloc)) THEN
         logger%close_global_unit_on_dealloc = close_global_unit_on_dealloc
         IF (PRESENT(default_global_unit_nr) .AND. PRESENT(global_filename) .AND. &
             (.NOT. close_global_unit_on_dealloc)) THEN
            logger%default_global_unit_nr = default_global_unit_nr
         END IF
      END IF

      IF (PRESENT(default_local_unit_nr)) &
         logger%default_local_unit_nr = default_local_unit_nr
      IF (PRESENT(local_filename)) THEN
         logger%local_filename = local_filename
         logger%close_local_unit_on_dealloc = .TRUE.
         logger%default_local_unit_nr = -1
      END IF
      IF (PRESENT(suffix)) logger%suffix = suffix

      IF (PRESENT(close_local_unit_on_dealloc)) THEN
         logger%close_local_unit_on_dealloc = close_local_unit_on_dealloc
         IF (PRESENT(default_local_unit_nr) .AND. PRESENT(local_filename) .AND. &
             (.NOT. close_local_unit_on_dealloc)) THEN
            logger%default_local_unit_nr = default_local_unit_nr
         END IF
      END IF

      IF (logger%default_local_unit_nr == -1) THEN
         IF (logger%para_env%is_source()) THEN
            logger%default_local_unit_nr = logger%default_global_unit_nr
            logger%close_local_unit_on_dealloc = .FALSE.
         END IF
      END IF
      IF (PRESENT(iter_info)) logger%iter_info => iter_info
      IF (ASSOCIATED(logger%iter_info)) THEN
         CALL cp_iteration_info_retain(logger%iter_info)
      ELSE
         CALL cp_iteration_info_create(logger%iter_info, "")
      END IF
   END SUBROUTINE cp_logger_create

! **************************************************************************************************
!> \brief retains the given logger (to be called to keep a shared copy of
!>      the logger)
!> \param logger the logger to retain
!> \par History
!>      08.2002 created [fawzi]
!> \author Fawzi Mohamed
! **************************************************************************************************
   SUBROUTINE cp_logger_retain(logger)
      TYPE(cp_logger_type), INTENT(INOUT)                :: logger

      CHARACTER(len=*), PARAMETER :: routineN = 'cp_logger_retain', &
         routineP = moduleN//':'//routineN

      IF (logger%ref_count < 1) &
         CPABORT(routineP//" logger%ref_count<1")
      logger%ref_count = logger%ref_count + 1
   END SUBROUTINE cp_logger_retain

! **************************************************************************************************
!> \brief releases this logger
!> \param logger the logger to release
!> \par History
!>      4.2002 created [fawzi]
!> \author Fawzi Mohamed
! **************************************************************************************************
   SUBROUTINE cp_logger_release(logger)
      TYPE(cp_logger_type), POINTER                      :: logger

      CHARACTER(len=*), PARAMETER :: routineN = 'cp_logger_release', &
         routineP = moduleN//':'//routineN

      IF (ASSOCIATED(logger)) THEN
         IF (logger%ref_count < 1) &
            CPABORT(routineP//" logger%ref_count<1")
         logger%ref_count = logger%ref_count - 1
         IF (logger%ref_count == 0) THEN
            IF (logger%close_global_unit_on_dealloc .AND. &
                logger%default_global_unit_nr >= 0) THEN
               CALL close_file(logger%default_global_unit_nr)
               logger%close_global_unit_on_dealloc = .FALSE.
               logger%default_global_unit_nr = -1
            END IF
            IF (logger%close_local_unit_on_dealloc .AND. &
                logger%default_local_unit_nr >= 0) THEN
               CALL close_file(logger%default_local_unit_nr)
               logger%close_local_unit_on_dealloc = .FALSE.
               logger%default_local_unit_nr = -1
            END IF
            CALL mp_para_env_release(logger%para_env)
            CALL cp_iteration_info_release(logger%iter_info)
            DEALLOCATE (logger)
         END IF
      END IF
      NULLIFY (logger)
   END SUBROUTINE cp_logger_release

! **************************************************************************************************
!> \brief this function can be called to check if the logger would log
!>      a message with the given level from the given source
!>      you should use this function if you do direct logging
!>      (without using cp_logger_log), or if you want to know if the generation
!>      of some costly log info is necessary
!> \param logger the logger you want to log in
!> \param level describes the of the message: cp_fatal_level(3),
!>     cp_failure_level(2), cp_warning_level(1), cp_note_level(0).
!> \return ...
!> \par History
!>      4.2002 revised [fawzi]
!> \author Fawzi Mohamed
! **************************************************************************************************
   FUNCTION cp_logger_would_log(logger, level) RESULT(res)
      TYPE(cp_logger_type), POINTER                      :: logger
      INTEGER, INTENT(in)                                :: level
      LOGICAL                                            :: res

      CHARACTER(len=*), PARAMETER :: routineN = 'cp_logger_would_log', &
         routineP = moduleN//':'//routineN

      TYPE(cp_logger_type), POINTER                      :: lggr

      lggr => logger
      IF (.NOT. ASSOCIATED(lggr)) lggr => cp_get_default_logger()
      IF (lggr%ref_count < 1) &
         CPABORT(routineP//" logger%ref_count<1")

      res = level >= lggr%print_level
   END FUNCTION cp_logger_would_log

! **************************************************************************************************
!> \brief returns the unit nr for the requested kind of log.
!> \param logger the logger you want to log in
!> \param local if true returns a local logger (one per task), otherwise
!>     returns a global logger (only the process with para_env%mepos==
!>     para_env%source should write to the global logger). Defaults to
!>     false
!> \return ...
!> \par History
!>      4.2002 revised [fawzi]
!> \author Fawzi Mohamed
! **************************************************************************************************
   FUNCTION cp_logger_get_unit_nr(logger, local) RESULT(res)
      TYPE(cp_logger_type), POINTER                      :: logger
      LOGICAL, INTENT(in), OPTIONAL                      :: local
      INTEGER                                            :: res

      res = cp_logger_get_default_unit_nr(logger, local=local)
   END FUNCTION cp_logger_get_unit_nr

! **************************************************************************************************
!> \brief returns the unit nr for the ionode (-1 on all other processors)
!>        skips as well checks if the procs calling this function is not the ionode
!> \param logger the logger you want to log in
!> \return ...
!> \par History
!>      12.2009 created [tlaino]
!> \author Teodoro Laino
! **************************************************************************************************
   FUNCTION cp_logger_get_default_io_unit(logger) RESULT(res)
      TYPE(cp_logger_type), OPTIONAL, POINTER            :: logger
      INTEGER                                            :: res

      TYPE(cp_logger_type), POINTER                      :: local_logger

      IF (PRESENT(logger)) THEN
         local_logger => logger
      ELSE IF (stack_pointer == 0) THEN
         res = -1 ! edge case: default logger not yet/anymore available
         RETURN
      ELSE
         local_logger => cp_get_default_logger()
      END IF

      res = cp_logger_get_default_unit_nr(local_logger, local=.FALSE., skip_not_ionode=.TRUE.)
   END FUNCTION cp_logger_get_default_io_unit

! *************************** cp_logger_type settings ***************************

! **************************************************************************************************
!> \brief changes the logging level. Log messages with a level less than the one
!>      given wo not be printed.
!> \param logger the logger to change
!> \param level the new logging level for the logger
!> \par History
!>      4.2002 revised [fawzi]
!> \author Fawzi Mohamed
! **************************************************************************************************
   SUBROUTINE cp_logger_set_log_level(logger, level)
      TYPE(cp_logger_type), INTENT(INOUT)                :: logger
      INTEGER, INTENT(in)                                :: level

      CHARACTER(len=*), PARAMETER :: routineN = 'cp_logger_set_log_level', &
         routineP = moduleN//':'//routineN

      IF (logger%ref_count < 1) &
         CPABORT(routineP//" logger%ref_count<1")
      logger%print_level = level
   END SUBROUTINE cp_logger_set_log_level

! **************************************************************************************************
!> \brief asks the default unit number of the given logger.
!>      try to use cp_logger_get_unit_nr
!> \param logger the logger you want info from
!> \param local if you want the local unit nr (defaults to false)
!> \param skip_not_ionode ...
!> \return ...
!> \par History
!>      4.2002 revised [fawzi]
!> \author Fawzi Mohamed
! **************************************************************************************************
   RECURSIVE FUNCTION cp_logger_get_default_unit_nr(logger, local, skip_not_ionode) RESULT(res)
      TYPE(cp_logger_type), OPTIONAL, POINTER            :: logger
      LOGICAL, INTENT(in), OPTIONAL                      :: local, skip_not_ionode
      INTEGER                                            :: res

      CHARACTER(len=*), PARAMETER :: routineN = 'cp_logger_get_default_unit_nr', &
         routineP = moduleN//':'//routineN

      CHARACTER(len=default_path_length)                 :: filename, host_name
      INTEGER                                            :: iostat, pid
      LOGICAL                                            :: loc, skip
      TYPE(cp_logger_type), POINTER                      :: lggr

      loc = .TRUE.
      skip = .FALSE.
      IF (PRESENT(logger)) THEN
         lggr => logger
      ELSE
         NULLIFY (lggr)
      END IF
      IF (.NOT. ASSOCIATED(lggr)) lggr => cp_get_default_logger()
      IF (lggr%ref_count < 1) &
         CPABORT(routineP//" logger%ref_count<1")

      IF (PRESENT(local)) loc = local
      IF (PRESENT(skip_not_ionode)) skip = skip_not_ionode
      IF (.NOT. loc) THEN
         IF (lggr%default_global_unit_nr <= 0) THEN
            IF (lggr%para_env%is_source()) THEN
               CALL cp_logger_generate_filename(lggr, filename, lggr%global_filename, &
                                                ".out", local=.FALSE.)
               CALL open_file(TRIM(filename), file_status="unknown", &
                              file_action="WRITE", file_position="APPEND", &
                              unit_number=lggr%default_global_unit_nr)
            ELSE IF (.NOT. skip) THEN
               lggr%default_global_unit_nr = cp_logger_get_default_unit_nr(lggr, .TRUE.)
               lggr%close_global_unit_on_dealloc = .FALSE.
            ELSE
               lggr%default_global_unit_nr = -1
               lggr%close_global_unit_on_dealloc = .FALSE.
            END IF
         END IF
         IF (.NOT. (lggr%para_env%is_source() .OR. skip)) THEN
            WRITE (UNIT=lggr%default_global_unit_nr, FMT='(/,T2,A)', IOSTAT=iostat) &
               ' *** WARNING non ionode asked for global logger ***'
            IF (iostat /= 0) THEN
               CALL m_getpid(pid)
               CALL m_hostnm(host_name)
               PRINT *, " *** Error trying to WRITE to the local logger ***"
               PRINT *, " *** MPI_id           = ", lggr%para_env%mepos
               PRINT *, " *** MPI_Communicator = ", lggr%para_env%get_handle()
               PRINT *, " *** PID              = ", pid
               PRINT *, " *** Hostname         = "//TRIM(host_name)
               CALL print_stack(default_output_unit)
            ELSE
               CALL print_stack(lggr%default_global_unit_nr)
            END IF
         END IF
         res = lggr%default_global_unit_nr
      ELSE
         IF (lggr%default_local_unit_nr <= 0) THEN
            CALL cp_logger_generate_filename(lggr, filename, lggr%local_filename, &
                                             ".out", local=.TRUE.)
            CALL open_file(TRIM(filename), file_status="unknown", &
                           file_action="WRITE", &
                           file_position="APPEND", &
                           unit_number=lggr%default_local_unit_nr)
            WRITE (UNIT=lggr%default_local_unit_nr, FMT='(/,T2,A,I0,A,I0,A)', IOSTAT=iostat) &
               '*** Local logger file of MPI task ', lggr%para_env%mepos, &
               ' in communicator ', lggr%para_env%get_handle(), ' ***'
            IF (iostat == 0) THEN
               CALL m_getpid(pid)
               CALL m_hostnm(host_name)
               WRITE (UNIT=lggr%default_local_unit_nr, FMT='(T2,A,I0)', IOSTAT=iostat) &
                  '*** PID      = ', pid, &
                  '*** Hostname = '//host_name
               CALL print_stack(lggr%default_local_unit_nr)
            END IF
            IF (iostat /= 0) THEN
               CALL m_getpid(pid)
               CALL m_hostnm(host_name)
               PRINT *, " *** Error trying to WRITE to the local logger ***"
               PRINT *, " *** MPI_id           = ", lggr%para_env%mepos
               PRINT *, " *** MPI_Communicator = ", lggr%para_env%get_handle()
               PRINT *, " *** PID              = ", pid
               PRINT *, " *** Hostname         = "//TRIM(host_name)
               CALL print_stack(default_output_unit)
            END IF

         END IF
         res = lggr%default_local_unit_nr
      END IF
   END FUNCTION cp_logger_get_default_unit_nr

! **************************************************************************************************
!> \brief generates a unique filename (ie adding eventual suffixes and
!>      process ids)
!> \param logger ...
!> \param res the resulting string
!> \param root the start of filename
!> \param postfix the end of the name
!> \param local if the name should be local to this task (defaults to false)
!> \par History
!>      08.2002 created [fawzi]
!> \author Fawzi Mohamed
!> \note
!>      this should be a function returning a variable length string.
!>      All spaces are moved to the end of the string.
!>      Not fully optimized: result must be a little longer than the
!>      resulting compressed filename
! **************************************************************************************************
   SUBROUTINE cp_logger_generate_filename(logger, res, root, postfix, &
                                          local)
      TYPE(cp_logger_type), POINTER                      :: logger
      CHARACTER(len=*), INTENT(inout)                    :: res
      CHARACTER(len=*), INTENT(in)                       :: root, postfix
      LOGICAL, INTENT(in), OPTIONAL                      :: local

      CHARACTER(len=*), PARAMETER :: routineN = 'cp_logger_generate_filename', &
         routineP = moduleN//':'//routineN

      LOGICAL                                            :: loc
      TYPE(cp_logger_type), POINTER                      :: lggr

      loc = .FALSE.
      res = ' '
      lggr => logger

      IF (.NOT. ASSOCIATED(lggr)) lggr => cp_get_default_logger()
      IF (lggr%ref_count < 1) &
         CPABORT(routineP//" logger%ref_count<1")
      IF (PRESENT(local)) loc = local
      IF (loc) THEN
         res = TRIM(root)//TRIM(lggr%suffix)//'_p'// &
               cp_to_string(lggr%para_env%mepos)//postfix
      ELSE
         res = TRIM(root)//TRIM(lggr%suffix)//postfix
      END IF
      CALL compress(res, full=.TRUE.)
   END SUBROUTINE cp_logger_generate_filename

! **************************************************************************************************
!> \brief sets various attributes of the given logger
!> \param logger the logger you want to change
!> \param local_filename the root of the name of the file used for local
!>        logging
!> \param global_filename the root of the name of the file used for
!>        global logging
!> \author Fawzi Mohamed
! **************************************************************************************************
   SUBROUTINE cp_logger_set(logger, local_filename, global_filename)
      TYPE(cp_logger_type), INTENT(INOUT)                :: logger
      CHARACTER(len=*), INTENT(in), OPTIONAL             :: local_filename, global_filename

      IF (PRESENT(local_filename)) logger%local_filename = local_filename
      IF (PRESENT(global_filename)) logger%global_filename = global_filename
   END SUBROUTINE cp_logger_set

! **************************************************************************************************
!> \brief converts an int to a string
!>      (should be a variable length string, but that does not work with
!>      all the compilers)
!> \param i the integer to convert
!> \param fmt Optional format string
!> \return ...
!> \par History
!>      4.2002 revised [fawzi]
!> \author Fawzi Mohamed, MK
! **************************************************************************************************
   FUNCTION cp_int_to_string(i, fmt) RESULT(res)
      INTEGER, INTENT(in)                                :: i
      CHARACTER(len=*), OPTIONAL                         :: fmt
      CHARACTER(len=25)                                  :: res

      CHARACTER(len=25)                                  :: t_res
      INTEGER                                            :: iostat
      REAL(KIND=dp)                                      :: tmp_r

      iostat = 0
      IF (PRESENT(fmt)) THEN
         WRITE (t_res, FMT=fmt, IOSTAT=iostat) i
      ELSE IF (i > 999999 .OR. i < -99999) THEN
         tmp_r = i
         WRITE (t_res, FMT='(ES8.1)', IOSTAT=iostat) tmp_r
      ELSE
         WRITE (t_res, FMT='(I6)', IOSTAT=iostat) i
      END IF
      res = t_res
      IF (iostat /= 0) THEN
         PRINT *, "cp_int_to_string I/O error", iostat
         CALL print_stack(cp_logger_get_default_unit_nr())
      END IF

   END FUNCTION cp_int_to_string

! **************************************************************************************************
!> \brief Convert a double precision real in a string
!>      (should be a variable length string, but that does not work with
!>      all the compilers)
!> \param val the number to convert
!> \param fmt Optional format string
!> \return ...
!> \par History
!>      4.2002 revised [fawzi]
!> \author Fawzi Mohamed, MK
! **************************************************************************************************
   FUNCTION cp_real_dp_to_string(val, fmt) RESULT(res)
      REAL(KIND=dp), INTENT(in)                          :: val
      CHARACTER(len=*), OPTIONAL                         :: fmt
      CHARACTER(len=25)                                  :: res

      INTEGER                                            :: iostat

      IF (PRESENT(fmt)) THEN
         WRITE (res, FMT=fmt, IOSTAT=iostat) val
      ELSE
         WRITE (res, FMT='(ES11.4)', IOSTAT=iostat) val
      END IF
      IF (iostat /= 0) THEN
         PRINT *, "cp_real_dp_to_string I/O error", iostat
         CALL print_stack(cp_logger_get_default_unit_nr())
      END IF

   END FUNCTION cp_real_dp_to_string

! **************************************************************************************************
!> \brief convert a logical in a string ('T' or 'F')
!> \param val the number to convert
!> \return ...
!> \author fawzi
! **************************************************************************************************
   ELEMENTAL FUNCTION cp_logical_to_string(val) RESULT(res)
      LOGICAL, INTENT(in)                                :: val
      CHARACTER(len=1)                                   :: res

      IF (val) THEN
         res = 'T'
      ELSE
         res = 'F'
      END IF
   END FUNCTION cp_logical_to_string

END MODULE cp_log_handling

