From d1d63cb7186273cc5d5de1ac92a9f1b78fb4571d Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Fri, 19 Sep 2025 18:02:18 +0200 Subject: [PATCH 1/5] fix: add get_log_level API and tests (fixes #1345) --- src/core/fortplot.f90 | 4 ++-- src/external/fortplot_logging.f90 | 10 +++++++- test/test_log_level_getter.f90 | 40 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 test/test_log_level_getter.f90 diff --git a/src/core/fortplot.f90 b/src/core/fortplot.f90 index a8dd8efe..a7edcd2e 100644 --- a/src/core/fortplot.f90 +++ b/src/core/fortplot.f90 @@ -63,7 +63,7 @@ module fortplot use fortplot_animation, only: animation_t, FuncAnimation ! Logging functionality - use fortplot_logging, only: set_log_level, & + use fortplot_logging, only: set_log_level, get_log_level, & LOG_LEVEL_SILENT, LOG_LEVEL_ERROR, & LOG_LEVEL_WARNING, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG @@ -144,7 +144,7 @@ module fortplot public :: animation_t, FuncAnimation ! Logging interface - public :: set_log_level + public :: set_log_level, get_log_level public :: LOG_LEVEL_SILENT, LOG_LEVEL_ERROR public :: LOG_LEVEL_WARNING, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG diff --git a/src/external/fortplot_logging.f90 b/src/external/fortplot_logging.f90 index 95edd80e..b05e9125 100644 --- a/src/external/fortplot_logging.f90 +++ b/src/external/fortplot_logging.f90 @@ -11,7 +11,7 @@ module fortplot_logging implicit none private - public :: set_log_level, log_info, log_warning, log_error, log_debug + public :: set_log_level, get_log_level, log_info, log_warning, log_error, log_debug public :: LOG_LEVEL_SILENT, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG public :: initialize_warning_suppression, is_warnings_suppressed @@ -41,6 +41,14 @@ subroutine set_log_level(level) current_log_level = level end subroutine set_log_level + function get_log_level() result(level) + !! Get the current global logging level + !! Returns one of: LOG_LEVEL_SILENT, LOG_LEVEL_ERROR, + !! LOG_LEVEL_WARNING, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG + integer :: level + level = current_log_level + end function get_log_level + subroutine log_info(message) !! Log an informational message character(len=*), intent(in) :: message diff --git a/test/test_log_level_getter.f90 b/test/test_log_level_getter.f90 new file mode 100644 index 00000000..aaa727c1 --- /dev/null +++ b/test/test_log_level_getter.f90 @@ -0,0 +1,40 @@ +program test_log_level_getter + !! Verify get_log_level() reflects the current logging level + !! and stays consistent across transitions. + + use fortplot, only: set_log_level, get_log_level, & + LOG_LEVEL_SILENT, LOG_LEVEL_ERROR, & + LOG_LEVEL_WARNING, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG + implicit none + + call ensure(get_log_level() == LOG_LEVEL_WARNING, 'default level is WARNING') + + call set_log_level(LOG_LEVEL_SILENT) + call ensure(get_log_level() == LOG_LEVEL_SILENT, 'level SILENT after set') + + call set_log_level(LOG_LEVEL_ERROR) + call ensure(get_log_level() == LOG_LEVEL_ERROR, 'level ERROR after set') + + call set_log_level(LOG_LEVEL_INFO) + call ensure(get_log_level() == LOG_LEVEL_INFO, 'level INFO after set') + + call set_log_level(LOG_LEVEL_DEBUG) + call ensure(get_log_level() == LOG_LEVEL_DEBUG, 'level DEBUG after set') + + ! Reset to default for other tests + call set_log_level(LOG_LEVEL_WARNING) + call ensure(get_log_level() == LOG_LEVEL_WARNING, 'level reset to WARNING') + +contains + + subroutine ensure(cond, msg) + logical, intent(in) :: cond + character(len=*), intent(in) :: msg + if (.not. cond) then + print *, 'ASSERTION FAILED:', trim(msg) + stop 1 + end if + end subroutine ensure + +end program test_log_level_getter + From 35119f5385b163ab5ce2c4751e1d54a73b3abe98 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Fri, 19 Sep 2025 18:05:50 +0200 Subject: [PATCH 2/5] docs: fix logging constant name in MPEG validation examples (use LOG_LEVEL_DEBUG) --- doc/mpeg_validation.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/mpeg_validation.md b/doc/mpeg_validation.md index 181597f5..a80590b3 100644 --- a/doc/mpeg_validation.md +++ b/doc/mpeg_validation.md @@ -61,7 +61,7 @@ end select - Output path validation - FFmpeg availability verification -**Stage 2: Pipeline Monitoring** +**Stage 2: Pipeline Monitoring** - Real-time pipe health during frame transmission - Frame data integrity checking - Memory usage and error detection @@ -84,15 +84,15 @@ Animation save now returns comprehensive status reflecting all validation stages subroutine enhanced_animation_save_example() type(animation_t) :: anim integer :: status - + call anim%save("animation.mp4", fps=24, status=status) - + select case (status) case (0) print *, "✓ Animation created and fully validated" case (-1) print *, "✗ FFmpeg not available" - case (-3) + case (-3) print *, "✗ Invalid file format" case (-4) print *, "✗ Pipe connection failed" @@ -167,7 +167,7 @@ File validation failed: ```fortran ! Enable detailed validation logging use fortplot_logging -call set_log_level(LOG_DEBUG) +call set_log_level(LOG_LEVEL_DEBUG) call anim%save("debug.mp4", fps=24, status=status) ! Outputs detailed pipe and validation diagnostics ``` @@ -266,7 +266,7 @@ type(animation_t) :: anim integer :: status ! Enable detailed diagnostics -call set_log_level(LOG_DEBUG) +call set_log_level(LOG_LEVEL_DEBUG) ! Save with enhanced error reporting call anim%save("debug.mp4", fps=24, status=status) @@ -282,4 +282,4 @@ end if - Frame transmission progress and validation - File system operations and validation results - FFmpeg command execution and output parsing -- Memory usage and performance metrics \ No newline at end of file +- Memory usage and performance metrics From 0aec82c3a3ab5cd1035b509961548e8927201881 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Fri, 19 Sep 2025 18:09:23 +0200 Subject: [PATCH 3/5] feat(logging): validate set_log_level input (clamp to valid range) and add test - Clamp input to [SILENT, DEBUG] to avoid undefined states - Add test_log_level_input_validation covering out-of-range handling No output/rendering changes; CI-fast tests pass. --- src/external/fortplot_logging.f90 | 66 +++++++++++++----------- test/test_log_level_input_validation.f90 | 38 ++++++++++++++ 2 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 test/test_log_level_input_validation.f90 diff --git a/src/external/fortplot_logging.f90 b/src/external/fortplot_logging.f90 index b05e9125..15d5e2f6 100644 --- a/src/external/fortplot_logging.f90 +++ b/src/external/fortplot_logging.f90 @@ -1,44 +1,50 @@ module fortplot_logging !! Simple logging facility for fortplot library !! Allows control over console output verbosity and warning suppression - !! + !! !! Supports environment variable-based warning suppression: !! - FORTPLOT_SUPPRESS_WARNINGS: Manual warning suppression control !! - Automatic CI detection: GITHUB_ACTIONS, CI, CONTINUOUS_INTEGRATION !! - FORTPLOT_FORCE_WARNINGS: Force warnings even in CI environments - + use fortplot_string_utils, only: parse_boolean_env implicit none private - + public :: set_log_level, get_log_level, log_info, log_warning, log_error, log_debug public :: LOG_LEVEL_SILENT, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG public :: initialize_warning_suppression, is_warnings_suppressed - + ! Log levels (in increasing verbosity) integer, parameter :: LOG_LEVEL_SILENT = 0 - integer, parameter :: LOG_LEVEL_ERROR = 1 + integer, parameter :: LOG_LEVEL_ERROR = 1 integer, parameter :: LOG_LEVEL_WARNING = 2 integer, parameter :: LOG_LEVEL_INFO = 3 integer, parameter :: LOG_LEVEL_DEBUG = 4 - + ! Default log level (warnings and errors only) integer :: current_log_level = LOG_LEVEL_WARNING - + ! Warning suppression state logical :: warnings_suppressed = .false. logical :: suppression_initialized = .false. - + contains subroutine set_log_level(level) - !! Set the global logging level - !! + !! Set the global logging level with input validation + !! !! Arguments: - !! level: LOG_LEVEL_SILENT, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, + !! level: LOG_LEVEL_SILENT, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, !! LOG_LEVEL_INFO, or LOG_LEVEL_DEBUG integer, intent(in) :: level - current_log_level = level + integer :: lvl + + ! Clamp to valid range to avoid undefined states + lvl = level + if (lvl < LOG_LEVEL_SILENT) lvl = LOG_LEVEL_SILENT + if (lvl > LOG_LEVEL_DEBUG) lvl = LOG_LEVEL_DEBUG + current_log_level = lvl end subroutine set_log_level function get_log_level() result(level) @@ -61,17 +67,17 @@ subroutine log_warning(message) !! Log a warning message with suppression support !! Respects FORTPLOT_SUPPRESS_WARNINGS and CI environment detection character(len=*), intent(in) :: message - + ! Initialize suppression state if not already done if (.not. suppression_initialized) then call initialize_warning_suppression() end if - + ! Check if warnings should be suppressed if (warnings_suppressed) then return ! Suppress warning output end if - + if (current_log_level >= LOG_LEVEL_WARNING) then print *, "[WARNING] ", trim(message) end if @@ -99,9 +105,9 @@ subroutine initialize_warning_suppression() character(len=256) :: env_value integer :: status logical :: ci_detected, force_warnings - + if (suppression_initialized) return - + ! Check for manual warning suppression override call get_environment_variable('FORTPLOT_SUPPRESS_WARNINGS', env_value, status=status) if (status == 0 .and. len_trim(env_value) > 0) then @@ -109,7 +115,7 @@ subroutine initialize_warning_suppression() suppression_initialized = .true. return end if - + ! Check for force warnings override (takes precedence) call get_environment_variable('FORTPLOT_FORCE_WARNINGS', env_value, status=status) if (status == 0 .and. len_trim(env_value) > 0) then @@ -120,7 +126,7 @@ subroutine initialize_warning_suppression() return end if end if - + ! Auto-detect CI environment ci_detected = detect_ci_environment() if (ci_detected) then @@ -128,17 +134,17 @@ subroutine initialize_warning_suppression() else warnings_suppressed = .false. end if - + suppression_initialized = .true. end subroutine initialize_warning_suppression - + logical function detect_ci_environment() !! Detect common CI environments character(len=256) :: env_value integer :: status - + detect_ci_environment = .false. - + ! GitHub Actions call get_environment_variable('GITHUB_ACTIONS', env_value, status=status) if (status == 0 .and. len_trim(env_value) > 0) then @@ -147,7 +153,7 @@ logical function detect_ci_environment() return end if end if - + ! Generic CI indicator call get_environment_variable('CI', env_value, status=status) if (status == 0 .and. len_trim(env_value) > 0) then @@ -156,7 +162,7 @@ logical function detect_ci_environment() return end if end if - + ! Jenkins CI call get_environment_variable('CONTINUOUS_INTEGRATION', env_value, status=status) if (status == 0 .and. len_trim(env_value) > 0) then @@ -165,14 +171,14 @@ logical function detect_ci_environment() return end if end if - + ! Jenkins BUILD_ID call get_environment_variable('BUILD_ID', env_value, status=status) if (status == 0 .and. len_trim(env_value) > 0) then detect_ci_environment = .true. return end if - + ! Travis CI call get_environment_variable('TRAVIS', env_value, status=status) if (status == 0 .and. len_trim(env_value) > 0) then @@ -181,7 +187,7 @@ logical function detect_ci_environment() return end if end if - + ! CircleCI call get_environment_variable('CIRCLECI', env_value, status=status) if (status == 0 .and. len_trim(env_value) > 0) then @@ -191,8 +197,8 @@ logical function detect_ci_environment() end if end if end function detect_ci_environment - - + + logical function is_warnings_suppressed() !! Check if warnings are currently suppressed if (.not. suppression_initialized) then diff --git a/test/test_log_level_input_validation.f90 b/test/test_log_level_input_validation.f90 new file mode 100644 index 00000000..3eafd4ec --- /dev/null +++ b/test/test_log_level_input_validation.f90 @@ -0,0 +1,38 @@ +program test_log_level_input_validation + !! Validate set_log_level() clamps inputs to the supported range + + use fortplot, only: set_log_level, get_log_level, & + LOG_LEVEL_SILENT, LOG_LEVEL_WARNING, LOG_LEVEL_DEBUG + implicit none + + ! Start from default (WARNING) + call ensure(get_log_level() == LOG_LEVEL_WARNING, 'default level is WARNING') + + ! Below minimum -> clamp to SILENT + call set_log_level(-999) + call ensure(get_log_level() == LOG_LEVEL_SILENT, 'clamp below min to SILENT') + + ! Within range -> keep as-is + call set_log_level(LOG_LEVEL_WARNING) + call ensure(get_log_level() == LOG_LEVEL_WARNING, 'keep valid level (WARNING)') + + ! Above maximum -> clamp to DEBUG + call set_log_level(999) + call ensure(get_log_level() == LOG_LEVEL_DEBUG, 'clamp above max to DEBUG') + + ! Reset to default for other tests + call set_log_level(LOG_LEVEL_WARNING) + +contains + + subroutine ensure(cond, msg) + logical, intent(in) :: cond + character(len=*), intent(in) :: msg + if (.not. cond) then + print *, 'ASSERTION FAILED:', trim(msg) + stop 1 + end if + end subroutine ensure + +end program test_log_level_input_validation + From 27f9cf8a9b29d7ea4577d5ab5b5207521c2fbc19 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Fri, 19 Sep 2025 18:11:24 +0200 Subject: [PATCH 4/5] docs(logging): document get_log_level() usage and safe restore pattern --- doc/warning_system.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/doc/warning_system.md b/doc/warning_system.md index f53d53b0..9a3a1150 100644 --- a/doc/warning_system.md +++ b/doc/warning_system.md @@ -148,8 +148,27 @@ FORTPLOT_SUPPRESS_WARNINGS=1 make test 2>&1 | grep WARNING 2. Verify CI detection: May be auto-suppressing 3. Set explicit control: `export FORTPLOT_SUPPRESS_WARNINGS=0` +## Querying And Restoring Log Level + +When temporarily increasing verbosity (for example during debugging), capture the +current level with `get_log_level()` and restore it afterwards to avoid leaking +state into other tests or applications: + +```fortran +use fortplot, only: set_log_level, get_log_level, & + LOG_LEVEL_DEBUG + +integer :: prev +prev = get_log_level() +call set_log_level(LOG_LEVEL_DEBUG) + +! ... perform debug operations ... + +call set_log_level(prev) +``` + ### Performance Impact The warning suppression system has minimal performance impact: - Environment variables checked once during initialization - Boolean flag checks for each warning (negligible overhead) -- No impact when warnings are suppressed \ No newline at end of file +- No impact when warnings are suppressed From a4e7388ce107896a4d4eacb340ea330d8a5b9c06 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Fri, 19 Sep 2025 18:12:18 +0200 Subject: [PATCH 5/5] fix(logging): make FORTPLOT_FORCE_WARNINGS override all suppression; docs: update precedence and examples --- doc/warning_system.md | 4 ++-- src/external/fortplot_logging.f90 | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/warning_system.md b/doc/warning_system.md index 9a3a1150..c6462510 100644 --- a/doc/warning_system.md +++ b/doc/warning_system.md @@ -42,8 +42,8 @@ The system automatically detects CI environments and suppresses warnings: ## Priority Order -1. **Manual Suppression**: `FORTPLOT_SUPPRESS_WARNINGS` takes highest priority -2. **Force Override**: `FORTPLOT_FORCE_WARNINGS` overrides CI auto-detection +1. **Force Override**: `FORTPLOT_FORCE_WARNINGS` takes highest priority and forces warnings ON +2. **Manual Suppression**: `FORTPLOT_SUPPRESS_WARNINGS` suppresses warnings when set 3. **CI Auto-Detection**: Automatic suppression in detected CI environments 4. **Default**: Warnings visible in development environments diff --git a/src/external/fortplot_logging.f90 b/src/external/fortplot_logging.f90 index 15d5e2f6..75ab622b 100644 --- a/src/external/fortplot_logging.f90 +++ b/src/external/fortplot_logging.f90 @@ -108,15 +108,7 @@ subroutine initialize_warning_suppression() if (suppression_initialized) return - ! Check for manual warning suppression override - call get_environment_variable('FORTPLOT_SUPPRESS_WARNINGS', env_value, status=status) - if (status == 0 .and. len_trim(env_value) > 0) then - warnings_suppressed = parse_boolean_env(env_value) - suppression_initialized = .true. - return - end if - - ! Check for force warnings override (takes precedence) + ! Check for force warnings override (takes precedence over all) call get_environment_variable('FORTPLOT_FORCE_WARNINGS', env_value, status=status) if (status == 0 .and. len_trim(env_value) > 0) then force_warnings = parse_boolean_env(env_value) @@ -127,6 +119,14 @@ subroutine initialize_warning_suppression() end if end if + ! Check for manual warning suppression override + call get_environment_variable('FORTPLOT_SUPPRESS_WARNINGS', env_value, status=status) + if (status == 0 .and. len_trim(env_value) > 0) then + warnings_suppressed = parse_boolean_env(env_value) + suppression_initialized = .true. + return + end if + ! Auto-detect CI environment ci_detected = detect_ci_environment() if (ci_detected) then