From 9bda9123ea4f740f8362791dc712741ccaf24179 Mon Sep 17 00:00:00 2001 From: Eric Ratliff Date: Thu, 19 Feb 2026 13:29:06 -0600 Subject: [PATCH] Added timestamps to project logging --- src/commands/refresh.rs | 1 + templates/basic/_monitor_filter.ps1 | 18 +++++ templates/basic/monitor.bat | 40 +++++++++-- templates/basic/monitor.sh | 41 ++++++++++- tests/integration_test.rs | 105 ++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 8 deletions(-) create mode 100644 templates/basic/_monitor_filter.ps1 diff --git a/src/commands/refresh.rs b/src/commands/refresh.rs index 458aef3..4610233 100644 --- a/src/commands/refresh.rs +++ b/src/commands/refresh.rs @@ -17,6 +17,7 @@ const REFRESHABLE_FILES: &[&str] = &[ "monitor.sh", "monitor.bat", "_detect_port.ps1", + "_monitor_filter.ps1", "test/run_tests.sh", "test/run_tests.bat", ]; diff --git a/templates/basic/_monitor_filter.ps1 b/templates/basic/_monitor_filter.ps1 new file mode 100644 index 0000000..5b12e13 --- /dev/null +++ b/templates/basic/_monitor_filter.ps1 @@ -0,0 +1,18 @@ +param( + [Parameter(Mandatory=$true)][string]$Port, + [Parameter(Mandatory=$true)][string]$Baud, + [switch]$Timestamps, + [string]$LogFile +) + +arduino-cli monitor -p $Port -c "baudrate=$Baud" | ForEach-Object { + $line = $_ + if ($Timestamps) { + $ts = Get-Date -Format "[HH:mm:ss.fff]" + $line = "$ts $line" + } + Write-Host $line + if ($LogFile) { + Add-Content -Path $LogFile -Value $line + } +} \ No newline at end of file diff --git a/templates/basic/monitor.bat b/templates/basic/monitor.bat index 5962377..ceae1e2 100644 --- a/templates/basic/monitor.bat +++ b/templates/basic/monitor.bat @@ -6,10 +6,12 @@ setlocal enabledelayedexpansion :: Reads baud rate from .anvil.toml. No Anvil binary required. :: :: Usage: -:: monitor.bat Open monitor (auto-detect port) -:: monitor.bat -p COM3 Specify port -:: monitor.bat -b 9600 Override baud rate -:: monitor.bat --board mega Use baud from a named board +:: monitor.bat Open monitor (auto-detect port) +:: monitor.bat -p COM3 Specify port +:: monitor.bat -b 9600 Override baud rate +:: monitor.bat --board mega Use baud from a named board +:: monitor.bat --timestamps Prepend [HH:MM:SS.mmm] to each line +:: monitor.bat --log session.log Also write output to a file set "SCRIPT_DIR=%~dp0" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%" @@ -60,6 +62,8 @@ if "%BAUD%"=="" set "BAUD=115200" :: -- Parse arguments ------------------------------------------------------ set "PORT=" set "BOARD_NAME=" +set "DO_TIMESTAMPS=0" +set "LOG_FILE=" :parse_args if "%~1"=="" goto done_args @@ -68,6 +72,8 @@ if "%~1"=="--port" set "PORT=%~2" & shift & shift & goto parse_args if "%~1"=="-b" set "BAUD=%~2" & shift & shift & goto parse_args if "%~1"=="--baud" set "BAUD=%~2" & shift & shift & goto parse_args if "%~1"=="--board" set "BOARD_NAME=%~2" & shift & shift & goto parse_args +if "%~1"=="--timestamps" set "DO_TIMESTAMPS=1" & shift & goto parse_args +if "%~1"=="--log" set "LOG_FILE=%~2" & shift & shift & goto parse_args if "%~1"=="--help" goto show_help if "%~1"=="-h" goto show_help echo FAIL: Unknown option: %~1 @@ -75,8 +81,11 @@ exit /b 1 :show_help echo Usage: monitor.bat [-p PORT] [-b BAUD] [--board NAME] +echo [--timestamps] [--log FILE] echo Opens serial monitor. Baud rate from .anvil.toml. -echo --board NAME selects a board from [boards.NAME]. +echo --board NAME selects a board from [boards.NAME]. +echo --timestamps prepend [HH:MM:SS.mmm] to each line. +echo --log FILE also write output to a file. exit /b 0 :done_args @@ -164,6 +173,27 @@ if "%PORT%"=="" ( :: -- Monitor -------------------------------------------------------------- echo Opening serial monitor on %PORT% at %BAUD% baud... +if "%DO_TIMESTAMPS%"=="1" echo Timestamps enabled. +if not "%LOG_FILE%"=="" echo Logging to: %LOG_FILE% echo Press Ctrl+C to exit. echo. + +if "%DO_TIMESTAMPS%"=="1" if not "%LOG_FILE%"=="" goto monitor_ts_log +if "%DO_TIMESTAMPS%"=="1" goto monitor_ts +if not "%LOG_FILE%"=="" goto monitor_log +goto monitor_plain + +:monitor_ts_log +powershell -NoProfile -File "%SCRIPT_DIR%\_monitor_filter.ps1" -Port "%PORT%" -Baud "%BAUD%" -Timestamps -LogFile "%LOG_FILE%" +goto :eof + +:monitor_ts +powershell -NoProfile -File "%SCRIPT_DIR%\_monitor_filter.ps1" -Port "%PORT%" -Baud "%BAUD%" -Timestamps +goto :eof + +:monitor_log +powershell -NoProfile -File "%SCRIPT_DIR%\_monitor_filter.ps1" -Port "%PORT%" -Baud "%BAUD%" -LogFile "%LOG_FILE%" +goto :eof + +:monitor_plain arduino-cli monitor -p %PORT% -c "baudrate=%BAUD%" \ No newline at end of file diff --git a/templates/basic/monitor.sh b/templates/basic/monitor.sh index 353ab0d..d1bc980 100644 --- a/templates/basic/monitor.sh +++ b/templates/basic/monitor.sh @@ -10,6 +10,8 @@ # ./monitor.sh -b 9600 Override baud rate # ./monitor.sh --board mega Use baud from a named board # ./monitor.sh --watch Reconnect after reset/replug +# ./monitor.sh --timestamps Prepend [HH:MM:SS.mmm] to each line +# ./monitor.sh --log session.log Also write output to a file # # Prerequisites: arduino-cli in PATH @@ -63,6 +65,8 @@ fi PORT="" DO_WATCH=0 BOARD_NAME="" +DO_TIMESTAMPS=0 +LOG_FILE="" while [[ $# -gt 0 ]]; do case "$1" in @@ -70,10 +74,15 @@ while [[ $# -gt 0 ]]; do -b|--baud) BAUD="$2"; shift 2 ;; --board) BOARD_NAME="$2"; shift 2 ;; --watch) DO_WATCH=1; shift ;; + --timestamps) DO_TIMESTAMPS=1; shift ;; + --log) LOG_FILE="$2"; shift 2 ;; -h|--help) echo "Usage: ./monitor.sh [-p PORT] [-b BAUD] [--board NAME] [--watch]" + echo " [--timestamps] [--log FILE]" echo " Opens serial monitor. Baud rate from .anvil.toml." - echo " --board NAME selects a board from [boards.NAME]." + echo " --board NAME selects a board from [boards.NAME]." + echo " --timestamps prepend [HH:MM:SS.mmm] to each line." + echo " --log FILE also write output to a file." exit 0 ;; *) die "Unknown option: $1" ;; @@ -180,10 +189,33 @@ if [[ -z "$PORT" ]]; then fi fi +# -- Output filter --------------------------------------------------------- +# Pipes stdin through optional timestamping and file logging. +monitor_filter() { + if [[ $DO_TIMESTAMPS -eq 1 ]] && [[ -n "$LOG_FILE" ]]; then + while IFS= read -r line; do + local ts + ts="[$(date '+%H:%M:%S.%3N')]" + printf "%s %s\n" "$ts" "$line" + printf "%s %s\n" "$ts" "$line" >> "$LOG_FILE" + done + elif [[ $DO_TIMESTAMPS -eq 1 ]]; then + while IFS= read -r line; do + printf "[%s] %s\n" "$(date '+%H:%M:%S.%3N')" "$line" + done + elif [[ -n "$LOG_FILE" ]]; then + tee -a "$LOG_FILE" + else + cat + fi +} + # -- Watch mode ------------------------------------------------------------ if [[ $DO_WATCH -eq 1 ]]; then echo "${CYN}${BLD}Persistent monitor on ${PORT} at ${BAUD} baud${RST}" echo "Reconnects after upload / reset / replug." + [[ $DO_TIMESTAMPS -eq 1 ]] && echo "Timestamps enabled." + [[ -n "$LOG_FILE" ]] && echo "Logging to: $LOG_FILE" echo "Press Ctrl+C to exit." echo "" @@ -191,7 +223,8 @@ if [[ $DO_WATCH -eq 1 ]]; then while true; do if [[ -e "$PORT" ]]; then - arduino-cli monitor -p "$PORT" -c "baudrate=$BAUD" 2>/dev/null || true + arduino-cli monitor -p "$PORT" -c "baudrate=$BAUD" 2>/dev/null \ + | monitor_filter || true echo "${YLW}--- ${PORT} disconnected ---${RST}" else echo "${CYN}--- Waiting for ${PORT} ...${RST}" @@ -205,7 +238,9 @@ if [[ $DO_WATCH -eq 1 ]]; then done else echo "Opening serial monitor on $PORT at $BAUD baud..." + [[ $DO_TIMESTAMPS -eq 1 ]] && echo "Timestamps enabled." + [[ -n "$LOG_FILE" ]] && echo "Logging to: $LOG_FILE" echo "Press Ctrl+C to exit." echo "" - arduino-cli monitor -p "$PORT" -c "baudrate=$BAUD" + arduino-cli monitor -p "$PORT" -c "baudrate=$BAUD" | monitor_filter fi \ No newline at end of file diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 9369ab2..a5ad3d7 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -831,6 +831,7 @@ fn test_full_project_structure() { "monitor.sh", "monitor.bat", "_detect_port.ps1", + "_monitor_filter.ps1", "test/CMakeLists.txt", "test/test_unit.cpp", "test/run_tests.sh", @@ -1414,6 +1415,7 @@ fn test_refresh_freshly_extracted_is_up_to_date() { "upload.sh", "upload.bat", "monitor.sh", "monitor.bat", "_detect_port.ps1", + "_monitor_filter.ps1", "test/run_tests.sh", "test/run_tests.bat", ]; @@ -1478,6 +1480,7 @@ fn test_refresh_does_not_list_user_files() { "upload.sh", "upload.bat", "monitor.sh", "monitor.bat", "_detect_port.ps1", + "_monitor_filter.ps1", "test/run_tests.sh", "test/run_tests.bat", ]; @@ -1865,4 +1868,106 @@ fn test_script_errors_mention_toml_section_syntax() { script ); } +} + +// ========================================================================== +// Monitor: --timestamps and --log flags +// ========================================================================== + +#[test] +fn test_monitor_scripts_accept_timestamps_flag() { + let tmp = TempDir::new().unwrap(); + let ctx = TemplateContext { + project_name: "ts_test".to_string(), + anvil_version: "1.0.0".to_string(), + board_name: "uno".to_string(), + fqbn: "arduino:avr:uno".to_string(), + baud: 115200, + }; + TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); + + for script in &["monitor.sh", "monitor.bat"] { + let content = fs::read_to_string(tmp.path().join(script)).unwrap(); + assert!( + content.contains("--timestamps"), + "{} should accept --timestamps flag", + script + ); + } +} + +#[test] +fn test_monitor_scripts_accept_log_flag() { + let tmp = TempDir::new().unwrap(); + let ctx = TemplateContext { + project_name: "log_test".to_string(), + anvil_version: "1.0.0".to_string(), + board_name: "uno".to_string(), + fqbn: "arduino:avr:uno".to_string(), + baud: 115200, + }; + TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); + + for script in &["monitor.sh", "monitor.bat"] { + let content = fs::read_to_string(tmp.path().join(script)).unwrap(); + assert!( + content.contains("--log"), + "{} should accept --log flag for file output", + script + ); + } +} + +#[test] +fn test_monitor_sh_has_timestamp_format() { + // The timestamp format should include hours, minutes, seconds, and millis + let tmp = TempDir::new().unwrap(); + let ctx = TemplateContext { + project_name: "ts_fmt".to_string(), + anvil_version: "1.0.0".to_string(), + board_name: "uno".to_string(), + fqbn: "arduino:avr:uno".to_string(), + baud: 115200, + }; + TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); + + let content = fs::read_to_string(tmp.path().join("monitor.sh")).unwrap(); + assert!( + content.contains("%H:%M:%S"), + "monitor.sh should use HH:MM:SS timestamp format" + ); + assert!( + content.contains("%3N"), + "monitor.sh should include milliseconds in timestamps" + ); +} + +#[test] +fn test_monitor_sh_timestamps_work_in_watch_mode() { + // The timestamp filter should also apply in --watch mode + let tmp = TempDir::new().unwrap(); + let ctx = TemplateContext { + project_name: "watch_ts".to_string(), + anvil_version: "1.0.0".to_string(), + board_name: "uno".to_string(), + fqbn: "arduino:avr:uno".to_string(), + baud: 115200, + }; + TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); + + let content = fs::read_to_string(tmp.path().join("monitor.sh")).unwrap(); + + // The filter function should be called in the watch loop + assert!( + content.contains("monitor_filter"), + "monitor.sh should use a filter function for timestamps" + ); + + // Count usages of monitor_filter - should appear in both watch and non-watch + let filter_count = content.matches("monitor_filter").count(); + assert!( + filter_count >= 3, + "monitor_filter should be defined and used in both watch and normal mode (found {} refs)", + filter_count + ); } \ No newline at end of file