A wrapper for Fortran-stdlib logger that enhances log messages with purpose classification and message categorization.
- Adds purpose tags to log messages (monitor/report/trace) to clarify the logging intent
- Supports message categorization for better log organization
- Fully compatible with Fortran-stdlib logger
- Extensible - easily add custom purposes and log levels
Traditional log levels (e.g. debug/info/warn/error) focus on message severity, but often fail to capture why we're logging in the first place. Flavanaly adds two key dimensions to logging:
- Purpose tags clarify why we're logging (e.g., monitoring system state, reporting results, or tracing execution flow)
- Categories help organize logs by subsystem or feature
This information makes logs more meaningful and more straightforward to filter/analyze.
- Modern Fortran compiler
- The compilers and versions listed below have been used to develop catechin.
- gfortran 11.2 bundled with quickstart Fortran on Windows
- Intel Fortran Classic 2021.5.0 Build 20211109_000000
- NAG Fortran 7.1 Build 7117
- Fortran-stdlib
- catechin is a wrapper for logger provided by the Fortran-stdlib.
- Fortran Package Manager (fpm) 0.7.0 alpha or later
- catechin is created as an fpm project.
- test-drive 0.4.0 or later
- FORD (optional)
To get the code, execute the following commnad:
git clone https://github.com/degawa/flavanaly.git
cd flavanaly
To build the library using fpm, execute the following command:
fpm build
Then, install the library using:
fpm install --prefix path/to/your/libdir
Add the following use
statement to modules or procedures calling Flavanaly.
use :: flavanaly
To use Flavanaly in your fpm project, add the following to the fpm.toml.
[dependencies]
flavanaly = {git = "https://github.com/degawa/flavanaly.git"}
Flavanaly provides a logging subroutine.
logging(purpose, level, message, module, procedure, category)
monitor
,report
, andtrace
are provided as specifiers for thepurpose
.debug
,info
,warn
, anderror
are provided as specifiers for thelevel
.message
,module
,procedure
,category
are strings.
An example is shown below:
use :: flavanaly
call logging(trace, info, "message", "main", category="IO")
! yyyy-mm-dd hh:mm:ss.xxx: main: INFO: (trace): [IO]: message
The purpose and category are added at the begging of the log message.
- Monitoring system state:
! Monitor memory usage
call logging(monitor, info, "Current memory usage: 80%", "memory_manager", category="System.Resources")
! yyyy-mm-dd hh:mm:ss.xxx: memory_manager: INFO: (monitor): [System.Resources]: Current memory usage: 80%
! Report calculation results
call logging(report, info, "Optimization converged after 100 iterations", "optimizer", category="Algorithm.Convergence")
! yyyy-mm-dd hh:mm:ss.xxx: optimizer: INFO: (report): [Algorithm.Convergence]: Optimization converged after 100 iterations
Since Flavanaly's purpose loggers (monitor
, report
, and trace
) extend the logger_type
defined in the stdlib_logger
module, they can be configured using procedures bound to the logger_type
:
add_log_file
add_log_unit
configure
remove_log_unit
Also, the current configuration can be confirmed using the type-bound procedures configuration
and log_units_assigned
.
program main
use, intrinsic :: iso_fortran_env, only: int32, output_unit
use :: flavanaly
use :: stdlib_logger, only:debug_level, information_level
implicit none
integer(int32) :: log_unit
call monitor%configure(level=debug_level)
call monitor%configure(time_stamp=.false.)
open (newunit=log_unit, file="monitor.log")
call monitor%add_log_unit(log_unit)
call monitor%add_log_unit(output_unit)
block
logical :: time_stamp
integer(int32) :: level, num_units
integer(int32), allocatable :: units(:)
call monitor%configuration(time_stamp=time_stamp, level=level, log_units=units)
num_units = monitor%log_units_assigned()
print *, "current status"
print *, "add time stamp: ", time_stamp
print *, "minimum level for printing log message: ", level
print *, "number of units assigned to monitor logger: ", num_units
print *, "log output units: ", units(:)
! current status
! add time stamp: F
! minimum level for printing log message: 10
! number of units assigned to monitor logger: 2
! log output units: -10 6
! ^ Log output unit numbers may change depending on the compiler and environment.
end block
call logging(monitor, debug, "message", category="TEST.IO.LOG")
!DEBUG: (monitor): [TEST.IO.LOG]: message
call monitor%remove_log_unit(log_unit, close_unit=.true.)
call monitor%configure(level=information_level)
call monitor%configure(time_stamp=.true.)
end program main
Flavanaly provides several preprocessor macros to customize the log format at compile time.
#define FLAVAN_PURPOSE_LENGTH 16 ! default value
Defines the maximum length of strings storing log purposes. Must be a positive integer. The default value is 16. If undefined or set to less than 1, it will be set to 16.
#define FLAVAN_PURPOSE_BRACKET "(" ! default value
#define FLAVAN_CATEGORY_BRACKET "[" ! default value
Define the opening brackets used for purpose and category tags in log messages.
FLAVAN_PURPOSE_BRACKET
: Opening bracket for purpose tags (default:"("
)FLAVAN_CATEGORY_BRACKET
: Opening bracket for category tags (default:"["
)
The corresponding closing brackets are automatically determined based on the following pairs:
()
,<>
,[]
,{}
: Each opening bracket is paired with its standard closing bracket- Other characters: The same character is used for both opening and closing brackets
Example log outputs with different bracket settings:
! Default settings
call logging(monitor, info, "message", category="System")
! Output: ... (monitor): [System]: message
! Custom settings
#define FLAVAN_PURPOSE_BRACKET "<"
#define FLAVAN_CATEGORY_BRACKET "{"
call logging(monitor, info, "message", category="System")
! Output: ... <monitor>: {System}: message
- Using compiler flags:
# gfortran example
gfortran -DFLAVAN_PURPOSE_LENGTH=32 -DFLAVAN_PURPOSE_BRACKET="{" source.f90
# Intel Fortran example
ifort /DFLAVAN_PURPOSE_LENGTH=32 /DFLAVAN_PURPOSE_BRACKET="{" source.f90
- Using fpm build command with the
--flag
option:
fpm build --flag "-DFLAVAN_PURPOSE_LENGTH=32 -DFLAVAN_PURPOSE_BRACKET='{'"
- All macro values must be set before including any Flavanaly modules
- Changes to macro values require recompiling the code
- Invalid bracket characters will default to using the same character for both opening and closing brackets
- Declare a
purposeful_logger_type
instance - Specify a custom purpose name
use :: flavanaly, only:purposeful_logger_type
type(purposeful_logger_type), public :: detail = purposeful_logger_type(purpose="detail")
- Define a new type extending
level_logger_atype
- Implement the required
logging
type-bound procedure - Declare an instance for use as a specifier
use :: flavanaly, only:level_logger_atype
! use clause must be described because it conflicts with logging procedures.
implicit none
type, private, extends(level_logger_atype) :: fatal_level_logger_type
contains
procedure, public, nopass :: logging
end type fatal_level_logger_type
type(fatal_level_logger_type), public :: fatal
contains
subroutine logging(logger, message, module, procedure)
use :: stdlib_logger, only:stdlib_logger_type => logger_type
class(stdlib_logger_type) , intent(in) :: logger
character(*) , intent(in) :: message
character(*) , intent(in), optional :: module
character(*) , intent(in), optional :: procedure
call logger%log_message(message, module, procedure, prefix="FATAL")
end subroutine logging
Passing the defined detail
and fatal
to logging
procedure yields the following output.
call logging(detail, fatal, "fatal error", "main", category="HID.controller.buttom")
! yyyy-mm-dd hh:mm:ss.xxx: main: FATAL: (detail): [HID.controller.buttom]: fatal error