From 0c2313b628298d14c2e961b531428314219f9d30 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 9 Apr 2026 11:52:11 +0000 Subject: [PATCH 01/23] feat: replace logger with lightweight console logging + ZIP export Replace the logger package dependency with an internal logging system that outputs to the R console and captures logs in memory. The session log can be exported as session_log.txt in the ZIP download. - Add inst/shiny/functions/logging.R with log_trace/debug/info/success/ warn/error functions, glue interpolation, configurable threshold via aNCA_LOG_LEVEL env var, and in-memory buffer - Remove setup_logger and log_debug_list from utils.R (now in logging.R) - Remove require(logger) from app.R - Move logger from Imports back to Suggests in DESCRIPTION - Add session_log to ZIP export tree and file filtering Closes #1210 Co-authored-by: Ona --- DESCRIPTION | 2 +- inst/shiny/app.R | 1 - inst/shiny/functions/logging.R | 76 ++++++++++++++++++++++++++++++++ inst/shiny/functions/utils.R | 42 ------------------ inst/shiny/functions/zip-utils.R | 21 +++++++++ inst/shiny/modules/tab_nca/zip.R | 5 ++- 6 files changed, 101 insertions(+), 46 deletions(-) create mode 100644 inst/shiny/functions/logging.R diff --git a/DESCRIPTION b/DESCRIPTION index 76abed03b..997eeab3f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -33,7 +33,6 @@ Imports: glue, htmltools, htmlwidgets, - logger, magrittr, PKNCA (>= 0.12.1), plotly (>= 4.11.0), @@ -67,6 +66,7 @@ Suggests: jsonlite, knitr, lintr (>= 3.2.0), + logger, markdown, mockery, nestcolor, diff --git a/inst/shiny/app.R b/inst/shiny/app.R index 6d9bef118..2fa5de7fb 100644 --- a/inst/shiny/app.R +++ b/inst/shiny/app.R @@ -3,7 +3,6 @@ require(aNCA) require(bslib) require(dplyr) require(htmlwidgets) -require(logger) require(formatters) require(magrittr) require(plotly) diff --git a/inst/shiny/functions/logging.R b/inst/shiny/functions/logging.R new file mode 100644 index 000000000..4a13a242c --- /dev/null +++ b/inst/shiny/functions/logging.R @@ -0,0 +1,76 @@ +# Lightweight logging system for the aNCA Shiny app. +# +# Replaces the `logger` package with console-only output and in-memory +# log capture for ZIP export. Supports glue-style interpolation and +# paste-style multi-argument calls. +# +# Log levels: TRACE < DEBUG < INFO < SUCCESS < WARN < ERROR +# Default threshold: INFO (configurable via aNCA_LOG_LEVEL env var). + +.log_env <- new.env(parent = emptyenv()) +.log_env$threshold <- "INFO" +.log_env$buffer <- character(0) + +.LOG_LEVELS <- c(TRACE = 1L, DEBUG = 2L, INFO = 3L, SUCCESS = 4L, WARN = 5L, ERROR = 6L) + +#' Initialise the logging system. +#' +#' Reads the threshold from the `aNCA_LOG_LEVEL` environment variable +#' (default `"INFO"`) and clears the in-memory log buffer. +setup_logger <- function() { + level <- toupper(Sys.getenv("aNCA_LOG_LEVEL", "INFO")) + if (!level %in% names(.LOG_LEVELS)) level <- "INFO" + .log_env$threshold <- level + .log_env$buffer <- character(0) +} + +#' Core logging function. +#' @param level Character: one of the log level names. +#' @param ... Message parts. If the first argument contains `{`, it is +#' evaluated as a glue string in the caller's environment. Otherwise +#' all arguments are pasted together. +#' @noRd +.log_msg <- function(level, ...) { + if (.LOG_LEVELS[[level]] < .LOG_LEVELS[[.log_env$threshold]]) return(invisible(NULL)) + + args <- list(...) + if (length(args) == 0L) { + msg <- "" + } else if (length(args) == 1L && grepl("\\{", args[[1]])) { + msg <- tryCatch( + glue::glue(args[[1]], .envir = parent.frame(2)), + error = function(e) paste0(args, collapse = "") + ) + } else { + msg <- paste0(args, collapse = "") + } + + timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%S") + line <- paste0("[", timestamp, "] ", level, ": ", msg) + + .log_env$buffer <- c(.log_env$buffer, line) + message(line) + invisible(NULL) +} + +# Public log functions matching the logger API +log_trace <- function(...) .log_msg("TRACE", ...) +log_debug <- function(...) .log_msg("DEBUG", ...) +log_info <- function(...) .log_msg("INFO", ...) +log_success <- function(...) .log_msg("SUCCESS", ...) +log_warn <- function(...) .log_msg("WARN", ...) +log_error <- function(...) .log_msg("ERROR", ...) + +#' Logs a list or data frame at DEBUG level. +#' +#' @param title Title for the log entry. +#' @param l List or data.frame to log. +log_debug_list <- function(title, l) { + log_debug(aNCA:::.concatenate_list(title, l)) +} + +#' Return the in-memory log buffer as a character vector. +#' @noRd +get_log_buffer <- function() { + .log_env$buffer +} diff --git a/inst/shiny/functions/utils.R b/inst/shiny/functions/utils.R index ae247ed42..ba264a160 100644 --- a/inst/shiny/functions/utils.R +++ b/inst/shiny/functions/utils.R @@ -1,45 +1,3 @@ -#' Sets up the logger package for the application. -#' -#' @details -#' The application logs everything to a log file located in `/log` directory. If such folder -#' does not exist, it will be created. Logfile for each session will be separate. The application -#' will keep 5 log files at any given time - if this number is exceeded, the oldest log file -#' will be deleted. -#' -#' In addition, information of the level specified by the user will be logged to console. -#' As a default, this level is INFO - this is so that the user has good information on what is -#' happening inside the app, but is not overwhelmed with tracing and debugging information. This -#' level can be changed using `aNCA_LOG_LEVEL` environmental variable, set for example in -#' `.Renviron` file. -setup_logger <- function() { - log_layout(layout_glue_colors) - log_formatter(formatter_glue) - log_threshold(TRACE) - log_threshold(Sys.getenv("aNCA_LOG_LEVEL", "INFO"), index = 2) - - log_dir <- "./log" - if (!dir.exists(log_dir)) dir.create(log_dir) - existing_logs <- list.files(log_dir, full.names = TRUE) - if (length(existing_logs) >= 5) file.remove(sort(existing_logs)[1]) # keep only five log files - logfile_name <- paste0(log_dir, "/aNCA_app_", format(Sys.time(), "%y%m%d-%H%M%S-"), ".log") - - log_appender(appender_file(logfile_name)) - log_appender(appender_console, index = 2) -} - -#' Logs a list and data frame objects. -#' -#' @details -#' Utilitary function for logging a list object (like mapping list or used settings) in -#' a nice format. Parses a list into nice string and logs at DEBUG level. Can also process -#' data frames, which will be converted into a list of rows. -#' -#' @param title Title for the logs. -#' @param l List object to be parsed into log. Can also be a data.frame. -log_debug_list <- function(title, l) { - log_debug(aNCA:::.concatenate_list(title, l)) -} - #' Needed to properly reset reactable.extras widgets #' #' @details diff --git a/inst/shiny/functions/zip-utils.R b/inst/shiny/functions/zip-utils.R index e6ce38f9a..f7b548e5f 100644 --- a/inst/shiny/functions/zip-utils.R +++ b/inst/shiny/functions/zip-utils.R @@ -426,6 +426,12 @@ prepare_export_files <- function(target_dir, detail = "Saving session info...") .export_session_info(target_dir) } + + if ("session_log" %in% input$res_tree) { + progress$set(message = "Creating exports...", + detail = "Saving session log...") + .export_session_log(target_dir) + } progress$inc(0.8) .clean_export_dir(target_dir, input, custom_names) @@ -638,6 +644,17 @@ prepare_export_files <- function(target_dir, writeLines(lines, file.path(target_dir, "session_info.txt")) } +#' Export the in-memory session log to a text file. +#' @param target_dir Target directory for the export. +#' @keywords internal +.export_session_log <- function(target_dir) { + log_buffer <- get_log_buffer() + if (length(log_buffer) == 0L) { + log_buffer <- "(No log entries captured during this session.)" + } + writeLines(log_buffer, file.path(target_dir, "session_log.txt")) +} + #' Clean Export Directory #' @param target_dir Target directory to clean #' @param input Shiny input object @@ -675,6 +692,10 @@ prepare_export_files <- function(target_dir, files_req <- c(files_req, grep("session_info\\.txt$", all_files, value = TRUE)) } + if ("session_log" %in% fnames) { + files_req <- c(files_req, grep("session_log\\.txt$", all_files, + value = TRUE)) + } file.remove(all_files[!all_files %in% files_req]) # Recursive directory cleanup — remove dirs that contain no files at any depth diff --git a/inst/shiny/modules/tab_nca/zip.R b/inst/shiny/modules/tab_nca/zip.R index e70b08f5d..142a9e668 100644 --- a/inst/shiny/modules/tab_nca/zip.R +++ b/inst/shiny/modules/tab_nca/zip.R @@ -658,7 +658,7 @@ zip_server <- function(id, res_nca, adnca_data, settings, grouping_vars) { } items$extras <- TREE_LIST$extras } else { - items$extras <- TREE_LIST$extras[c("settings_file", "session_info")] + items$extras <- TREE_LIST$extras[c("settings_file", "session_info", "session_log")] } items @@ -693,7 +693,8 @@ TREE_LIST <- list( results_slides = "", r_script = "", settings_file = "", - session_info = "" + session_info = "", + session_log = "" ) ) From 41b9403391f286685d33d3468db330dd92ac7a80 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:03:20 +0000 Subject: [PATCH 02/23] feat: add log-points for NCA settings changes Log analyte, specimen, profile, method, min half-life points, and flag rule changes at INFO level. Closes #1219 (partial) Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/settings.R | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/inst/shiny/modules/tab_nca/setup/settings.R b/inst/shiny/modules/tab_nca/setup/settings.R index 1a2d80642..9fdac1834 100644 --- a/inst/shiny/modules/tab_nca/setup/settings.R +++ b/inst/shiny/modules/tab_nca/setup/settings.R @@ -273,6 +273,8 @@ settings_server <- function(id, data, adnca_data, settings_override) { updating_filters(TRUE) on.exit(updating_filters(FALSE)) + log_info("Analyte selection changed: ", paste(input$select_analyte, collapse = ", ")) + settings <- .consume_settings() all_pcspec <- unique(data()$PCSPEC) %>% na.omit() @@ -307,6 +309,8 @@ settings_server <- function(id, data, adnca_data, settings_override) { updating_filters(TRUE) on.exit(updating_filters(FALSE)) + log_info("Specimen selection changed: ", paste(input$select_pcspec, collapse = ", ")) + settings <- .consume_settings() all_analyte <- unique(data()$PARAM) %>% na.omit() @@ -328,6 +332,15 @@ settings_server <- function(id, data, adnca_data, settings_override) { .update_profile(settings) }) + # Log method and min half-life points changes + observeEvent(input$method, { + log_info("Extrapolation method changed: {input$method}") + }, ignoreInit = TRUE) + + observeEvent(input$min_hl_points, { + log_info("Min. half-life points changed: {input$min_hl_points}") + }, ignoreInit = TRUE) + # Include keyboard limits for the settings GUI display # Keyboard limits for the setting thresholds @@ -337,6 +350,18 @@ settings_server <- function(id, data, adnca_data, settings_override) { limit_input_value(input, session, "LAMZSPN_threshold", min = 0, lab = "LAMZSPN") limit_input_value(input, session, "min_hl_points", max = 10, min = 2, lab = "Min. HL Points") + # Log flag rule changes + .flag_names <- c("R2ADJ", "R2", "AUCPEO", "AUCPEP", "LAMZSPN") + lapply(.flag_names, function(flag) { + observeEvent(input[[paste0(flag, "_rule")]], { + state <- if (input[[paste0(flag, "_rule")]]) "enabled" else "disabled" + log_info("Flag rule {flag} {state}") + }, ignoreInit = TRUE) + observeEvent(input[[paste0(flag, "_threshold")]], { + log_info("Flag rule {flag} threshold changed: {input[[paste0(flag, '_threshold')]]}") + }, ignoreInit = TRUE) + }) + # Reactive value to store the partial intervals data table # Define the parameters that can be used for partial area calculations PARTIAL_INT_PARAMS <- metadata_nca_parameters %>% From 5922af8ffd0047a68f9d54740858de59de9b5056 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:03:32 +0000 Subject: [PATCH 03/23] feat: add debug log-point for selected parameter names Log the full list of selected parameters at DEBUG level alongside the existing INFO count message. Closes #1219 (partial) Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/parameter_selection.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inst/shiny/modules/tab_nca/setup/parameter_selection.R b/inst/shiny/modules/tab_nca/setup/parameter_selection.R index 5d5e860fe..474a3a808 100644 --- a/inst/shiny/modules/tab_nca/setup/parameter_selection.R +++ b/inst/shiny/modules/tab_nca/setup/parameter_selection.R @@ -287,6 +287,9 @@ parameter_selection_server <- function(id, processed_pknca_data, parameter_overr n_params <- if (is.null(params)) 0 else length(params) log_info("Parameter selection for '{study_type}': {n_params} parameters selected.") + if (n_params > 0) { + log_debug("Parameters for '{study_type}': ", paste(params, collapse = ", ")) + } }, ignoreNULL = FALSE, ignoreInit = TRUE) }) }) From 7ce0384c37e37b97a3d0db161590a5725293ab4f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:04:00 +0000 Subject: [PATCH 04/23] feat: add log-points for slope selector user actions Upgrade slope rule add/remove from TRACE to INFO level. Add DEBUG summary of slope rules count on table changes. Closes #1219 (partial) Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/manual_slopes_table.R | 4 ++-- inst/shiny/modules/tab_nca/setup/slope_selector.R | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index 5475c0466..88bd9db3f 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -93,7 +93,7 @@ manual_slopes_table_server <- function( # Add a new row to the table when the user clicks the add button observeEvent(input$add_rule, { - log_trace("{id}: adding manual slopes row") + log_info("Slope selector: adding manual slope rule") first_group <- slopes_pknca_groups()[1, ] time_col <- pknca_data()$conc$columns$time new_row <- cbind( @@ -126,7 +126,7 @@ manual_slopes_table_server <- function( # Remove selected rows from the table when the user clicks the remove button observeEvent(input$remove_rule, { - log_trace("{id}: removing manual slopes row") + log_info("Slope selector: removing manual slope rule") selected <- getReactableState("manual_slopes", "selected") req(selected) edited_slopes <- manual_slopes()[-selected, ] diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 4a926a7c5..a903b76b6 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -250,6 +250,11 @@ slope_selector_server <- function( # nolint observeEvent(manual_slopes(), { req(manual_slopes()) + n_rules <- nrow(manual_slopes()) + n_excl <- sum(manual_slopes()$TYPE == "Exclusion", na.rm = TRUE) + n_incl <- n_rules - n_excl + log_debug("Slope rules updated: {n_rules} total ({n_incl} inclusions, {n_excl} exclusions)") + # Update reactable with rules reactable::updateReactable( outputId = "manual_slopes", From ef5b7def557de68ae04304c08833b2704e19dff7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:04:39 +0000 Subject: [PATCH 05/23] feat: add log-points for general exclusion changes Log exclusion add (with row count, type, reason), removal, and settings restore at INFO level. Closes #1219 (partial) Co-authored-by: Ona --- .../modules/tab_nca/setup/general_exclusions.R | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/inst/shiny/modules/tab_nca/setup/general_exclusions.R b/inst/shiny/modules/tab_nca/setup/general_exclusions.R index 157258962..6de6c2220 100644 --- a/inst/shiny/modules/tab_nca/setup/general_exclusions.R +++ b/inst/shiny/modules/tab_nca/setup/general_exclusions.R @@ -131,6 +131,7 @@ general_exclusions_server <- function( xbtn_counter(max(new_ids)) exclusion_list(rehydrated_list) + log_info("Exclusions restored from settings: ", length(overrides), " rules loaded") } }) @@ -156,6 +157,21 @@ general_exclusions_server <- function( # Add a new exclusion when the Add button is pressed observeEvent(input$add_exclusion_reason, { + rows_sel <- getReactableState("conc_table-table", "selected") + reason <- input$exclusion_reason + nca_checked <- isTRUE(input$cb_manual_nca) + tlg_checked <- isTRUE(input$cb_tlg) + + if (length(rows_sel) > 0 && nzchar(reason) && (nca_checked || tlg_checked)) { + type_label <- if (nca_checked && tlg_checked) "NCA + TLG" + else if (nca_checked) "NCA" + else "TLG" + log_info( + "Exclusion added: ", length(rows_sel), " rows, type=", type_label, + ", reason='", reason, "'" + ) + } + .handle_add_exclusion( input, session, exclusion_list, xbtn_counter ) @@ -167,6 +183,7 @@ general_exclusions_server <- function( lapply(lst, function(item) { xbtn_id <- item$xbtn_id observeEvent(input[[xbtn_id]], { + log_info("Exclusion removed: reason='", item$reason, "'") current <- exclusion_list() exclusion_list( Filter(function(x) x$xbtn_id != xbtn_id, current) From 4a4b92dd6e4f30a699e44f18fc07a7df1486bb76 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:05:02 +0000 Subject: [PATCH 06/23] feat: add log-points for profile selection and data imputation Log NCA profile changes, BLQ strategy changes, and C0 imputation toggle at INFO level. Closes #1219 (partial) Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/data_imputation.R | 9 +++++++++ inst/shiny/modules/tab_nca/setup/settings.R | 1 + 2 files changed, 10 insertions(+) diff --git a/inst/shiny/modules/tab_nca/setup/data_imputation.R b/inst/shiny/modules/tab_nca/setup/data_imputation.R index eb5c5f6f9..065952f1c 100644 --- a/inst/shiny/modules/tab_nca/setup/data_imputation.R +++ b/inst/shiny/modules/tab_nca/setup/data_imputation.R @@ -196,6 +196,15 @@ data_imputation_server <- function(id, settings_override) { } }) + observeEvent(input$select_blq_strategy, { + log_info("BLQ imputation strategy changed: {input$select_blq_strategy}") + }, ignoreInit = TRUE) + + observeEvent(input$should_impute_c0, { + state <- if (isTRUE(input$should_impute_c0)) "enabled" else "disabled" + log_info("Start concentration imputation {state}") + }, ignoreInit = TRUE) + blq_imputation_rule <- reactive({ req(input$select_blq_strategy) rule_list <- switch(input$select_blq_strategy, diff --git a/inst/shiny/modules/tab_nca/setup/settings.R b/inst/shiny/modules/tab_nca/setup/settings.R index 9fdac1834..c4b5eb9ba 100644 --- a/inst/shiny/modules/tab_nca/setup/settings.R +++ b/inst/shiny/modules/tab_nca/setup/settings.R @@ -260,6 +260,7 @@ settings_server <- function(id, data, adnca_data, settings_override) { available_choices = profile_choices, override_val = settings$profile ) + log_info("NCA profile selection changed: ", paste(target_profile, collapse = ", ")) updatePickerInput( session, "select_profile", choices = profile_choices, selected = target_profile From 91ca90b5b2e2149bfbe0a216f03e2e7a0f6d852b Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 13:20:38 +0000 Subject: [PATCH 07/23] fix: use paste-style logging to avoid glue parent.frame issues Replace all glue-style log calls ({var}) with paste-style (multiple arguments) to avoid parent.frame(2) scoping issues in closures and observeEvent callbacks. Guard profile log against NA values. Co-authored-by: Ona --- .../modules/tab_nca/setup/data_imputation.R | 4 ++-- .../tab_nca/setup/parameter_selection.R | 2 +- inst/shiny/modules/tab_nca/setup/settings.R | 23 +++++++++++-------- .../modules/tab_nca/setup/slope_selector.R | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/data_imputation.R b/inst/shiny/modules/tab_nca/setup/data_imputation.R index 065952f1c..a793c08ba 100644 --- a/inst/shiny/modules/tab_nca/setup/data_imputation.R +++ b/inst/shiny/modules/tab_nca/setup/data_imputation.R @@ -197,12 +197,12 @@ data_imputation_server <- function(id, settings_override) { }) observeEvent(input$select_blq_strategy, { - log_info("BLQ imputation strategy changed: {input$select_blq_strategy}") + log_info("BLQ imputation strategy changed: ", input$select_blq_strategy) }, ignoreInit = TRUE) observeEvent(input$should_impute_c0, { state <- if (isTRUE(input$should_impute_c0)) "enabled" else "disabled" - log_info("Start concentration imputation {state}") + log_info("Start concentration imputation ", state) }, ignoreInit = TRUE) blq_imputation_rule <- reactive({ diff --git a/inst/shiny/modules/tab_nca/setup/parameter_selection.R b/inst/shiny/modules/tab_nca/setup/parameter_selection.R index 474a3a808..5b9b8df80 100644 --- a/inst/shiny/modules/tab_nca/setup/parameter_selection.R +++ b/inst/shiny/modules/tab_nca/setup/parameter_selection.R @@ -288,7 +288,7 @@ parameter_selection_server <- function(id, processed_pknca_data, parameter_overr log_info("Parameter selection for '{study_type}': {n_params} parameters selected.") if (n_params > 0) { - log_debug("Parameters for '{study_type}': ", paste(params, collapse = ", ")) + log_debug("Parameters for '", study_type, "': ", paste(params, collapse = ", ")) } }, ignoreNULL = FALSE, ignoreInit = TRUE) }) diff --git a/inst/shiny/modules/tab_nca/setup/settings.R b/inst/shiny/modules/tab_nca/setup/settings.R index c4b5eb9ba..a6817ea6b 100644 --- a/inst/shiny/modules/tab_nca/setup/settings.R +++ b/inst/shiny/modules/tab_nca/setup/settings.R @@ -260,7 +260,9 @@ settings_server <- function(id, data, adnca_data, settings_override) { available_choices = profile_choices, override_val = settings$profile ) - log_info("NCA profile selection changed: ", paste(target_profile, collapse = ", ")) + if (length(target_profile) > 0 && !anyNA(target_profile)) { + log_info("NCA profile selection changed: ", paste(target_profile, collapse = ", ")) + } updatePickerInput( session, "select_profile", choices = profile_choices, selected = target_profile @@ -335,11 +337,11 @@ settings_server <- function(id, data, adnca_data, settings_override) { # Log method and min half-life points changes observeEvent(input$method, { - log_info("Extrapolation method changed: {input$method}") + log_info("Extrapolation method changed: ", input$method) }, ignoreInit = TRUE) observeEvent(input$min_hl_points, { - log_info("Min. half-life points changed: {input$min_hl_points}") + log_info("Min. half-life points changed: ", input$min_hl_points) }, ignoreInit = TRUE) # Include keyboard limits for the settings GUI display @@ -352,14 +354,15 @@ settings_server <- function(id, data, adnca_data, settings_override) { limit_input_value(input, session, "min_hl_points", max = 10, min = 2, lab = "Min. HL Points") # Log flag rule changes - .flag_names <- c("R2ADJ", "R2", "AUCPEO", "AUCPEP", "LAMZSPN") - lapply(.flag_names, function(flag) { - observeEvent(input[[paste0(flag, "_rule")]], { - state <- if (input[[paste0(flag, "_rule")]]) "enabled" else "disabled" - log_info("Flag rule {flag} {state}") + lapply(c("R2ADJ", "R2", "AUCPEO", "AUCPEP", "LAMZSPN"), function(flag) { + rule_id <- paste0(flag, "_rule") + threshold_id <- paste0(flag, "_threshold") + observeEvent(input[[rule_id]], { + state <- if (input[[rule_id]]) "enabled" else "disabled" + log_info("Flag rule ", flag, " ", state) }, ignoreInit = TRUE) - observeEvent(input[[paste0(flag, "_threshold")]], { - log_info("Flag rule {flag} threshold changed: {input[[paste0(flag, '_threshold')]]}") + observeEvent(input[[threshold_id]], { + log_info("Flag rule ", flag, " threshold changed: ", input[[threshold_id]]) }, ignoreInit = TRUE) }) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index a903b76b6..7be204110 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -253,7 +253,7 @@ slope_selector_server <- function( # nolint n_rules <- nrow(manual_slopes()) n_excl <- sum(manual_slopes()$TYPE == "Exclusion", na.rm = TRUE) n_incl <- n_rules - n_excl - log_debug("Slope rules updated: {n_rules} total ({n_incl} inclusions, {n_excl} exclusions)") + log_debug("Slope rules updated: ", n_rules, " total (", n_incl, " inclusions, ", n_excl, " exclusions)") # Update reactable with rules reactable::updateReactable( From 47f533b8f87551b9f3480e766062ca3bf05e26fd Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:03:20 +0000 Subject: [PATCH 08/23] feat: add log-points for NCA settings changes Log analyte, specimen, profile, method, min half-life points, and flag rule changes at INFO level. Closes #1219 (partial) Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/settings.R | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/inst/shiny/modules/tab_nca/setup/settings.R b/inst/shiny/modules/tab_nca/setup/settings.R index b8818a9f4..e9f78c707 100644 --- a/inst/shiny/modules/tab_nca/setup/settings.R +++ b/inst/shiny/modules/tab_nca/setup/settings.R @@ -301,6 +301,8 @@ settings_server <- function(id, data, adnca_data, settings_override) { updating_filters(TRUE) on.exit(updating_filters(FALSE)) + log_info("Analyte selection changed: ", paste(input$select_analyte, collapse = ", ")) + settings <- .consume_settings() all_pcspec <- unique(data()$PCSPEC) %>% na.omit() @@ -335,6 +337,8 @@ settings_server <- function(id, data, adnca_data, settings_override) { updating_filters(TRUE) on.exit(updating_filters(FALSE)) + log_info("Specimen selection changed: ", paste(input$select_pcspec, collapse = ", ")) + settings <- .consume_settings() all_analyte <- unique(data()$PARAM) %>% na.omit() @@ -356,6 +360,15 @@ settings_server <- function(id, data, adnca_data, settings_override) { .update_profile(settings) }) + # Log method and min half-life points changes + observeEvent(input$method, { + log_info("Extrapolation method changed: {input$method}") + }, ignoreInit = TRUE) + + observeEvent(input$min_hl_points, { + log_info("Min. half-life points changed: {input$min_hl_points}") + }, ignoreInit = TRUE) + # Include keyboard limits for the settings GUI display # Keyboard limits for the setting thresholds @@ -365,6 +378,18 @@ settings_server <- function(id, data, adnca_data, settings_override) { limit_input_value(input, session, "LAMZSPN_threshold", min = 0, lab = "LAMZSPN") limit_input_value(input, session, "min_hl_points", max = 10, min = 2, lab = "Min. HL Points") + # Log flag rule changes + .flag_names <- c("R2ADJ", "R2", "AUCPEO", "AUCPEP", "LAMZSPN") + lapply(.flag_names, function(flag) { + observeEvent(input[[paste0(flag, "_rule")]], { + state <- if (input[[paste0(flag, "_rule")]]) "enabled" else "disabled" + log_info("Flag rule {flag} {state}") + }, ignoreInit = TRUE) + observeEvent(input[[paste0(flag, "_threshold")]], { + log_info("Flag rule {flag} threshold changed: {input[[paste0(flag, '_threshold')]]}") + }, ignoreInit = TRUE) + }) + # Reactive value to store the partial intervals data table # Define the parameters that can be used for partial area calculations PARTIAL_INT_PARAMS <- metadata_nca_parameters %>% From 550e0bb0f5d4570e580fc6a482e8178fed1bbbe0 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:03:32 +0000 Subject: [PATCH 09/23] feat: add debug log-point for selected parameter names Log the full list of selected parameters at DEBUG level alongside the existing INFO count message. Closes #1219 (partial) Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/parameter_selection.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inst/shiny/modules/tab_nca/setup/parameter_selection.R b/inst/shiny/modules/tab_nca/setup/parameter_selection.R index 5d5e860fe..474a3a808 100644 --- a/inst/shiny/modules/tab_nca/setup/parameter_selection.R +++ b/inst/shiny/modules/tab_nca/setup/parameter_selection.R @@ -287,6 +287,9 @@ parameter_selection_server <- function(id, processed_pknca_data, parameter_overr n_params <- if (is.null(params)) 0 else length(params) log_info("Parameter selection for '{study_type}': {n_params} parameters selected.") + if (n_params > 0) { + log_debug("Parameters for '{study_type}': ", paste(params, collapse = ", ")) + } }, ignoreNULL = FALSE, ignoreInit = TRUE) }) }) From b3785048dd3e4a30872cae259722f2151e82a04c Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:04:00 +0000 Subject: [PATCH 10/23] feat: add log-points for slope selector user actions Upgrade slope rule add/remove from TRACE to INFO level. Add DEBUG summary of slope rules count on table changes. Closes #1219 (partial) Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/manual_slopes_table.R | 4 ++-- inst/shiny/modules/tab_nca/setup/slope_selector.R | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index 5475c0466..88bd9db3f 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -93,7 +93,7 @@ manual_slopes_table_server <- function( # Add a new row to the table when the user clicks the add button observeEvent(input$add_rule, { - log_trace("{id}: adding manual slopes row") + log_info("Slope selector: adding manual slope rule") first_group <- slopes_pknca_groups()[1, ] time_col <- pknca_data()$conc$columns$time new_row <- cbind( @@ -126,7 +126,7 @@ manual_slopes_table_server <- function( # Remove selected rows from the table when the user clicks the remove button observeEvent(input$remove_rule, { - log_trace("{id}: removing manual slopes row") + log_info("Slope selector: removing manual slope rule") selected <- getReactableState("manual_slopes", "selected") req(selected) edited_slopes <- manual_slopes()[-selected, ] diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 7f3cfe0f1..15575e5e1 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -272,6 +272,11 @@ slope_selector_server <- function( # nolint observeEvent(manual_slopes(), { req(manual_slopes()) + n_rules <- nrow(manual_slopes()) + n_excl <- sum(manual_slopes()$TYPE == "Exclusion", na.rm = TRUE) + n_incl <- n_rules - n_excl + log_debug("Slope rules updated: {n_rules} total ({n_incl} inclusions, {n_excl} exclusions)") + # Update reactable with rules reactable::updateReactable( outputId = "manual_slopes", From 12273876fe4fbd98e496993b577805bbe2b3cf9c Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:04:39 +0000 Subject: [PATCH 11/23] feat: add log-points for general exclusion changes Log exclusion add (with row count, type, reason), removal, and settings restore at INFO level. Closes #1219 (partial) Co-authored-by: Ona --- .../modules/tab_nca/setup/general_exclusions.R | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/inst/shiny/modules/tab_nca/setup/general_exclusions.R b/inst/shiny/modules/tab_nca/setup/general_exclusions.R index 157258962..6de6c2220 100644 --- a/inst/shiny/modules/tab_nca/setup/general_exclusions.R +++ b/inst/shiny/modules/tab_nca/setup/general_exclusions.R @@ -131,6 +131,7 @@ general_exclusions_server <- function( xbtn_counter(max(new_ids)) exclusion_list(rehydrated_list) + log_info("Exclusions restored from settings: ", length(overrides), " rules loaded") } }) @@ -156,6 +157,21 @@ general_exclusions_server <- function( # Add a new exclusion when the Add button is pressed observeEvent(input$add_exclusion_reason, { + rows_sel <- getReactableState("conc_table-table", "selected") + reason <- input$exclusion_reason + nca_checked <- isTRUE(input$cb_manual_nca) + tlg_checked <- isTRUE(input$cb_tlg) + + if (length(rows_sel) > 0 && nzchar(reason) && (nca_checked || tlg_checked)) { + type_label <- if (nca_checked && tlg_checked) "NCA + TLG" + else if (nca_checked) "NCA" + else "TLG" + log_info( + "Exclusion added: ", length(rows_sel), " rows, type=", type_label, + ", reason='", reason, "'" + ) + } + .handle_add_exclusion( input, session, exclusion_list, xbtn_counter ) @@ -167,6 +183,7 @@ general_exclusions_server <- function( lapply(lst, function(item) { xbtn_id <- item$xbtn_id observeEvent(input[[xbtn_id]], { + log_info("Exclusion removed: reason='", item$reason, "'") current <- exclusion_list() exclusion_list( Filter(function(x) x$xbtn_id != xbtn_id, current) From dacc0c2c98e2c68d76a34b54f4ccf2733b01626f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:05:02 +0000 Subject: [PATCH 12/23] feat: add log-points for profile selection and data imputation Log NCA profile changes, BLQ strategy changes, and C0 imputation toggle at INFO level. Closes #1219 (partial) Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/data_imputation.R | 9 +++++++++ inst/shiny/modules/tab_nca/setup/settings.R | 1 + 2 files changed, 10 insertions(+) diff --git a/inst/shiny/modules/tab_nca/setup/data_imputation.R b/inst/shiny/modules/tab_nca/setup/data_imputation.R index eb5c5f6f9..065952f1c 100644 --- a/inst/shiny/modules/tab_nca/setup/data_imputation.R +++ b/inst/shiny/modules/tab_nca/setup/data_imputation.R @@ -196,6 +196,15 @@ data_imputation_server <- function(id, settings_override) { } }) + observeEvent(input$select_blq_strategy, { + log_info("BLQ imputation strategy changed: {input$select_blq_strategy}") + }, ignoreInit = TRUE) + + observeEvent(input$should_impute_c0, { + state <- if (isTRUE(input$should_impute_c0)) "enabled" else "disabled" + log_info("Start concentration imputation {state}") + }, ignoreInit = TRUE) + blq_imputation_rule <- reactive({ req(input$select_blq_strategy) rule_list <- switch(input$select_blq_strategy, diff --git a/inst/shiny/modules/tab_nca/setup/settings.R b/inst/shiny/modules/tab_nca/setup/settings.R index e9f78c707..4b0bbf535 100644 --- a/inst/shiny/modules/tab_nca/setup/settings.R +++ b/inst/shiny/modules/tab_nca/setup/settings.R @@ -288,6 +288,7 @@ settings_server <- function(id, data, adnca_data, settings_override) { available_choices = profile_choices, override_val = settings$profile ) + log_info("NCA profile selection changed: ", paste(target_profile, collapse = ", ")) updatePickerInput( session, "select_profile", choices = profile_choices, selected = target_profile From 893084b29126588a55753acbda649cfb4c317894 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 13:20:38 +0000 Subject: [PATCH 13/23] fix: use paste-style logging to avoid glue parent.frame issues Replace all glue-style log calls ({var}) with paste-style (multiple arguments) to avoid parent.frame(2) scoping issues in closures and observeEvent callbacks. Guard profile log against NA values. Co-authored-by: Ona --- .../modules/tab_nca/setup/data_imputation.R | 4 ++-- .../tab_nca/setup/parameter_selection.R | 2 +- inst/shiny/modules/tab_nca/setup/settings.R | 23 +++++++++++-------- .../modules/tab_nca/setup/slope_selector.R | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/data_imputation.R b/inst/shiny/modules/tab_nca/setup/data_imputation.R index 065952f1c..a793c08ba 100644 --- a/inst/shiny/modules/tab_nca/setup/data_imputation.R +++ b/inst/shiny/modules/tab_nca/setup/data_imputation.R @@ -197,12 +197,12 @@ data_imputation_server <- function(id, settings_override) { }) observeEvent(input$select_blq_strategy, { - log_info("BLQ imputation strategy changed: {input$select_blq_strategy}") + log_info("BLQ imputation strategy changed: ", input$select_blq_strategy) }, ignoreInit = TRUE) observeEvent(input$should_impute_c0, { state <- if (isTRUE(input$should_impute_c0)) "enabled" else "disabled" - log_info("Start concentration imputation {state}") + log_info("Start concentration imputation ", state) }, ignoreInit = TRUE) blq_imputation_rule <- reactive({ diff --git a/inst/shiny/modules/tab_nca/setup/parameter_selection.R b/inst/shiny/modules/tab_nca/setup/parameter_selection.R index 474a3a808..5b9b8df80 100644 --- a/inst/shiny/modules/tab_nca/setup/parameter_selection.R +++ b/inst/shiny/modules/tab_nca/setup/parameter_selection.R @@ -288,7 +288,7 @@ parameter_selection_server <- function(id, processed_pknca_data, parameter_overr log_info("Parameter selection for '{study_type}': {n_params} parameters selected.") if (n_params > 0) { - log_debug("Parameters for '{study_type}': ", paste(params, collapse = ", ")) + log_debug("Parameters for '", study_type, "': ", paste(params, collapse = ", ")) } }, ignoreNULL = FALSE, ignoreInit = TRUE) }) diff --git a/inst/shiny/modules/tab_nca/setup/settings.R b/inst/shiny/modules/tab_nca/setup/settings.R index 4b0bbf535..0b4d39fc1 100644 --- a/inst/shiny/modules/tab_nca/setup/settings.R +++ b/inst/shiny/modules/tab_nca/setup/settings.R @@ -288,7 +288,9 @@ settings_server <- function(id, data, adnca_data, settings_override) { available_choices = profile_choices, override_val = settings$profile ) - log_info("NCA profile selection changed: ", paste(target_profile, collapse = ", ")) + if (length(target_profile) > 0 && !anyNA(target_profile)) { + log_info("NCA profile selection changed: ", paste(target_profile, collapse = ", ")) + } updatePickerInput( session, "select_profile", choices = profile_choices, selected = target_profile @@ -363,11 +365,11 @@ settings_server <- function(id, data, adnca_data, settings_override) { # Log method and min half-life points changes observeEvent(input$method, { - log_info("Extrapolation method changed: {input$method}") + log_info("Extrapolation method changed: ", input$method) }, ignoreInit = TRUE) observeEvent(input$min_hl_points, { - log_info("Min. half-life points changed: {input$min_hl_points}") + log_info("Min. half-life points changed: ", input$min_hl_points) }, ignoreInit = TRUE) # Include keyboard limits for the settings GUI display @@ -380,14 +382,15 @@ settings_server <- function(id, data, adnca_data, settings_override) { limit_input_value(input, session, "min_hl_points", max = 10, min = 2, lab = "Min. HL Points") # Log flag rule changes - .flag_names <- c("R2ADJ", "R2", "AUCPEO", "AUCPEP", "LAMZSPN") - lapply(.flag_names, function(flag) { - observeEvent(input[[paste0(flag, "_rule")]], { - state <- if (input[[paste0(flag, "_rule")]]) "enabled" else "disabled" - log_info("Flag rule {flag} {state}") + lapply(c("R2ADJ", "R2", "AUCPEO", "AUCPEP", "LAMZSPN"), function(flag) { + rule_id <- paste0(flag, "_rule") + threshold_id <- paste0(flag, "_threshold") + observeEvent(input[[rule_id]], { + state <- if (input[[rule_id]]) "enabled" else "disabled" + log_info("Flag rule ", flag, " ", state) }, ignoreInit = TRUE) - observeEvent(input[[paste0(flag, "_threshold")]], { - log_info("Flag rule {flag} threshold changed: {input[[paste0(flag, '_threshold')]]}") + observeEvent(input[[threshold_id]], { + log_info("Flag rule ", flag, " threshold changed: ", input[[threshold_id]]) }, ignoreInit = TRUE) }) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 15575e5e1..dd4f6ce8e 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -275,7 +275,7 @@ slope_selector_server <- function( # nolint n_rules <- nrow(manual_slopes()) n_excl <- sum(manual_slopes()$TYPE == "Exclusion", na.rm = TRUE) n_incl <- n_rules - n_excl - log_debug("Slope rules updated: {n_rules} total ({n_incl} inclusions, {n_excl} exclusions)") + log_debug("Slope rules updated: ", n_rules, " total (", n_incl, " inclusions, ", n_excl, " exclusions)") # Update reactable with rules reactable::updateReactable( From 0eb099fc4b4d8c4b37b0f512aaa6e5a1f65e32b6 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:05:46 +0000 Subject: [PATCH 14/23] feat: add header block to exported session_log.txt Include explanation of what the log captures, current threshold, visible levels, and a link to the full reference page. Closes #1220 (partial) Co-authored-by: Ona --- inst/shiny/functions/zip-utils.R | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/inst/shiny/functions/zip-utils.R b/inst/shiny/functions/zip-utils.R index 54feab18b..8054ec202 100644 --- a/inst/shiny/functions/zip-utils.R +++ b/inst/shiny/functions/zip-utils.R @@ -651,10 +651,38 @@ prepare_export_files <- function(target_dir, #' @keywords internal .export_session_log <- function(target_dir) { log_buffer <- get_log_buffer() + + threshold <- .log_env$threshold + header <- c( + "# aNCA Session Log", + "#", + "# This file contains application events captured during your session.", + "# It records data upload, mapping, NCA settings, parameter selection,", + "# slope adjustments, exclusions, calculation results, and exports.", + "#", + "# Warnings and errors from these operations are included when caught", + "# by the application. Unexpected R errors or warnings from third-party", + "# packages that are not explicitly handled will NOT appear here.", + "#", + paste0("# Log level: ", threshold, + " (configurable via aNCA_LOG_LEVEL env var)"), + paste0("# Levels shown at current threshold: ", + paste(names(.LOG_LEVELS)[.LOG_LEVELS >= .LOG_LEVELS[[threshold]]], + collapse = ", ")), + "#", + "# For a full reference of logged events, see:", + "# https://pharmaverse.github.io/aNCA/articles/session_log.html", + "#", + paste0("# Generated: ", format(Sys.time(), "%Y-%m-%d %H:%M:%S %Z")), + "# -------------------------------------------------------------------", + "" + ) + if (length(log_buffer) == 0L) { log_buffer <- "(No log entries captured during this session.)" } - writeLines(log_buffer, file.path(target_dir, "session_log.txt")) + + writeLines(c(header, log_buffer), file.path(target_dir, "session_log.txt")) } #' Clean Export Directory From a12157389655dee927399f42fcb4d04e21e5dd6e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:06:23 +0000 Subject: [PATCH 15/23] docs: add session log reference vignette Developer website page documenting all logged events by workflow stage, log levels, and configuration via aNCA_LOG_LEVEL. Closes #1220 (partial) Co-authored-by: Ona --- vignettes/session_log.Rmd | 201 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 vignettes/session_log.Rmd diff --git a/vignettes/session_log.Rmd b/vignettes/session_log.Rmd new file mode 100644 index 000000000..183575b3f --- /dev/null +++ b/vignettes/session_log.Rmd @@ -0,0 +1,201 @@ +--- +title: "Session Log Reference" +description: > + Reference for the session log exported with aNCA results. + Describes what events are captured, log levels, and configuration. +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Session Log Reference} + %\VignetteEngine{knitr::knitr} + %\VignetteEncoding{UTF-8} +--- + +When you export results as a ZIP file, aNCA includes a `session_log.txt` that +records application events from your session. This page explains what the log +contains, how to read it, and how to configure its verbosity. + +## What the log captures + +The session log records events emitted by explicit logging calls placed +throughout the aNCA Shiny application. Each line follows this format: + +``` +[2025-04-30 09:40:16] INFO: Calculating NCA results... +``` + +The log captures **application-level events** — not raw R console output. +Only messages that pass through aNCA's internal `log_*()` functions appear +in the file. + +### What is NOT captured + +- Unexpected R warnings or errors that are not wrapped in a `tryCatch` block + with a corresponding `log_warn()` or `log_error()` call. +- Messages from third-party packages (e.g., `PKNCA`, `dplyr`) unless the + application explicitly catches and logs them. +- Raw `cat()` or `print()` output from any source. + +## Log levels + +aNCA uses six log levels, ordered from most to least verbose: + +| Level | Purpose | +|---|---| +| **TRACE** | Fine-grained internal operations (module attachment, reactive updates) | +| **DEBUG** | Detailed diagnostic data (parameter lists, slope rule dumps) | +| **INFO** | Key user-facing operations (data upload, NCA calculation, setting changes) | +| **SUCCESS** | Completion confirmations (data loaded, results calculated) | +| **WARN** | Non-fatal issues (calculation warnings, incompatible settings) | +| **ERROR** | Failures (calculation errors, file load failures) | + +The default threshold is **INFO**, meaning only INFO, SUCCESS, WARN, and ERROR +messages appear. Set `aNCA_LOG_LEVEL=TRACE` or `aNCA_LOG_LEVEL=DEBUG` in your +`.Renviron` file to see more detail. + +## Logged events by workflow stage + +The following table lists all events currently captured in the session log, +organized by the stage of the workflow where they occur. + +### Startup + +| Event | Level | +|---|---| +| Application startup | INFO | +| Application restarting | INFO | + +### Data Upload + +| Event | Level | +|---|---| +| Data upload initialized (with file path) | INFO | +| All user data loaded | SUCCESS | +| File loading errors | ERROR | +| User data binding errors | ERROR | + +### Settings Restore + +| Event | Level | +|---|---| +| Settings restored from version | SUCCESS | +| Settings loaded from file | SUCCESS | +| Settings load failure | ERROR | +| Incompatible settings (analyte, profile, specimen) | WARN | + +### Data Mapping + +| Event | Level | +|---|---| +| Processing data mapping | INFO | +| Data mapping warnings | WARN | +| Data mapping errors | ERROR | + +### Data Filtering + +| Event | Level | +|---|---| +| Data filtering warnings | WARN | + +### NCA Settings + +| Event | Level | +|---|---| +| Analyte selection changed | INFO | +| Specimen selection changed | INFO | +| NCA profile selection changed | INFO | +| Extrapolation method changed | INFO | +| Min. half-life points changed | INFO | +| Flag rule enabled/disabled | INFO | +| Flag rule threshold changed | INFO | +| BLQ imputation strategy changed | INFO | +| Start concentration imputation toggled | INFO | + +### Parameter Selection + +| Event | Level | +|---|---| +| Parameter count per study type | INFO | +| Full parameter list per study type | DEBUG | + +### Slope Selector + +| Event | Level | +|---|---| +| Module server attachment | TRACE | +| Plotly click detected | TRACE | +| Manual slope rule added | INFO | +| Manual slope rule removed | INFO | +| Slope rules summary (inclusions/exclusions count) | DEBUG | +| Manual slopes override applied | DEBUG | +| Manual slopes override incompatible | WARN | +| Slope edit table rendering | TRACE | + +### General Exclusions + +| Event | Level | +|---|---| +| Exclusion added (row count, type, reason) | INFO | +| Exclusion removed | INFO | +| Exclusions restored from settings | INFO | + +### NCA Calculation + +| Event | Level | +|---|---| +| Creating / updating PKNCA data object | TRACE | +| PKNCA data object created | SUCCESS | +| PKNCA data object creation error | ERROR | +| Updating parameter selection data | TRACE | +| Calculating NCA results | INFO | +| NCA calculation warnings | WARN | +| NCA results calculated | SUCCESS | +| NCA calculation error | ERROR | +| Invalid parameters | ERROR | + +### Exploration Plots + +| Event | Level | +|---|---| +| Rendering individual plots | INFO | +| Computing mean plot | INFO | +| Rendering boxplot | INFO | +| Exploration plot saved (with overwrite flag) | INFO | +| Exploration plot removed | INFO | + +### TLG Generation + +| Event | Level | +|---|---| +| TLG module server attachment | TRACE | +| Submitted TLG list | DEBUG | +| TLG list rendering error | ERROR | + +### Export + +| Event | Level | +|---|---| +| Downloading summary statistics as CSV | INFO | + +### Units & Other + +| Event | Level | +|---|---| +| Applying custom units specification | TRACE | + +## Configuring the log level + +Set the `aNCA_LOG_LEVEL` environment variable before starting the app. +Valid values: `TRACE`, `DEBUG`, `INFO` (default), `SUCCESS`, `WARN`, `ERROR`. + +In your `.Renviron` file: + +``` +aNCA_LOG_LEVEL=DEBUG +``` + +Or set it in R before launching: + +```r +Sys.setenv(aNCA_LOG_LEVEL = "TRACE") +aNCA::run_app() +``` From 6400c057c63f8cb97cfc21aa7ff1ebcac756ecd7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:06:40 +0000 Subject: [PATCH 16/23] docs: add session log page to pkgdown navbar Add 'Session Log Reference' entry under Developer Resources in the website navigation. Closes #1220 (partial) Co-authored-by: Ona --- pkgdown/_pkgdown.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 815656825..392b6ecfe 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -29,6 +29,8 @@ navbar: href: articles/adding_tlg.html - text: Design & Architecture href: articles/design.html + - text: Session Log Reference + href: articles/session_log.html - text: Functions manual href: reference/index.html project: From f62eaf291725923bd94ae852b1d87ea4232f5b75 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 14 Apr 2026 10:06:55 +0000 Subject: [PATCH 17/23] docs: add scope and reference link to logging.R header Clarify what the log captures vs. what it does not, and link to the full session log reference page. Closes #1220 (partial) Co-authored-by: Ona --- inst/shiny/functions/logging.R | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/inst/shiny/functions/logging.R b/inst/shiny/functions/logging.R index 4a13a242c..ce29dedbc 100644 --- a/inst/shiny/functions/logging.R +++ b/inst/shiny/functions/logging.R @@ -6,6 +6,14 @@ # # Log levels: TRACE < DEBUG < INFO < SUCCESS < WARN < ERROR # Default threshold: INFO (configurable via aNCA_LOG_LEVEL env var). +# +# The log captures application-level events only — not raw R console +# output. Warnings and errors from third-party packages appear only +# when explicitly caught by tryCatch blocks with log_warn/log_error. +# +# The in-memory buffer is exported as session_log.txt in the ZIP +# download. For a full reference of logged events, see: +# https://pharmaverse.github.io/aNCA/articles/session_log.html .log_env <- new.env(parent = emptyenv()) .log_env$threshold <- "INFO" From b26843bb3132024bba7559971d4d78bda3906daf Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 20 Apr 2026 13:43:42 +0200 Subject: [PATCH 18/23] refactor lintr --- inst/shiny/modules/tab_tlg.R | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/inst/shiny/modules/tab_tlg.R b/inst/shiny/modules/tab_tlg.R index 83bcbaf0e..278219205 100644 --- a/inst/shiny/modules/tab_tlg.R +++ b/inst/shiny/modules/tab_tlg.R @@ -250,7 +250,11 @@ tab_tlg_server <- function(id, data) { panels <- lapply(tlg_order_graphs, function(g_id) { graph_ui <- { g_def <- .TLG_DEFINITIONS[[g_id]] - module_id <- paste0(g_id, paste0(sample(c(letters, 0:9), 5, replace = TRUE), collapse = "")) + module_id <- paste0( + g_id, + paste0(sample(c(letters, 0:9), 5, replace = TRUE), + collapse = "") + ) if (exists(g_def$fun)) { tlg_module_server(module_id, data, "graph", get(g_def$fun), g_def$options) @@ -288,7 +292,11 @@ tab_tlg_server <- function(id, data) { panels <- lapply(tlg_order_listings, function(g_id) { list_ui <- { g_def <- .TLG_DEFINITIONS[[g_id]] - module_id <- paste0(g_id, paste0(sample(c(letters, 0:9), 5, replace = TRUE), collapse = "")) + module_id <- paste0( + g_id, + paste0(sample(c(letters, 0:9), 5, replace = TRUE), + collapse = "") + ) if (exists(g_def$fun)) { tlg_module_server(module_id, data, "listing", get(g_def$fun), g_def$options) From 2a347e1898c18bf4ef0e93f7aac156f616150c62 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 20 Apr 2026 11:46:38 +0000 Subject: [PATCH 19/23] refactor: unify slope selector logging into single table-change message Replace separate add/remove INFO messages with a single INFO summary that fires on any slope table change (button clicks, plotly point selections, cell edits). Demote add/remove to TRACE. Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/manual_slopes_table.R | 4 ++-- inst/shiny/modules/tab_nca/setup/slope_selector.R | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index 88bd9db3f..c70de323f 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -93,7 +93,7 @@ manual_slopes_table_server <- function( # Add a new row to the table when the user clicks the add button observeEvent(input$add_rule, { - log_info("Slope selector: adding manual slope rule") + log_trace("Slope selector: adding manual slope rule") first_group <- slopes_pknca_groups()[1, ] time_col <- pknca_data()$conc$columns$time new_row <- cbind( @@ -126,7 +126,7 @@ manual_slopes_table_server <- function( # Remove selected rows from the table when the user clicks the remove button observeEvent(input$remove_rule, { - log_info("Slope selector: removing manual slope rule") + log_trace("Slope selector: removing manual slope rule") selected <- getReactableState("manual_slopes", "selected") req(selected) edited_slopes <- manual_slopes()[-selected, ] diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index dd4f6ce8e..dbef48589 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -275,7 +275,7 @@ slope_selector_server <- function( # nolint n_rules <- nrow(manual_slopes()) n_excl <- sum(manual_slopes()$TYPE == "Exclusion", na.rm = TRUE) n_incl <- n_rules - n_excl - log_debug("Slope rules updated: ", n_rules, " total (", n_incl, " inclusions, ", n_excl, " exclusions)") + log_info("Slope rules updated: ", n_rules, " total (", n_incl, " inclusions, ", n_excl, " exclusions)") # Update reactable with rules reactable::updateReactable( From 1cb62440b42d7c919414488120044645c99007b9 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 23 Apr 2026 07:32:04 +0000 Subject: [PATCH 20/23] fix: address review feedback on log-points - Suppress analyte/specimen/profile logs during initial data load using a filters_initialized guard - Move profile log to dedicated observeEvent(input$select_profile) with ignoreInit = TRUE - Move exclusion-add logging inside .handle_add_exclusion to avoid duplicating input reads - Use 'selections' instead of 'inclusions' in slope log to match the UI terminology (TYPE == 'Selection') Co-authored-by: Ona --- inst/shiny/functions/utils-exclusions.R | 6 ++++ .../tab_nca/setup/general_exclusions.R | 15 ---------- inst/shiny/modules/tab_nca/setup/settings.R | 28 ++++++++++++++----- .../modules/tab_nca/setup/slope_selector.R | 2 +- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/inst/shiny/functions/utils-exclusions.R b/inst/shiny/functions/utils-exclusions.R index 95c543eca..c2cdf22e4 100644 --- a/inst/shiny/functions/utils-exclusions.R +++ b/inst/shiny/functions/utils-exclusions.R @@ -90,6 +90,12 @@ EXCL_COLOR_BOTH <- "#FFD9B3" # orange — NCA + TLG exclusion_list(append(current, list_new_reason)) updateTextInput(session, "exclusion_reason", value = "") updateReactable(table_id, selected = NA) + + type_label <- .exclusion_type_label(nca_checked, tlg_checked) + log_info( + "Exclusion added: ", length(rows_sel), " rows, type=", type_label, + ", reason='", reason, "'" + ) } } diff --git a/inst/shiny/modules/tab_nca/setup/general_exclusions.R b/inst/shiny/modules/tab_nca/setup/general_exclusions.R index 6de6c2220..4fe331d3e 100644 --- a/inst/shiny/modules/tab_nca/setup/general_exclusions.R +++ b/inst/shiny/modules/tab_nca/setup/general_exclusions.R @@ -157,21 +157,6 @@ general_exclusions_server <- function( # Add a new exclusion when the Add button is pressed observeEvent(input$add_exclusion_reason, { - rows_sel <- getReactableState("conc_table-table", "selected") - reason <- input$exclusion_reason - nca_checked <- isTRUE(input$cb_manual_nca) - tlg_checked <- isTRUE(input$cb_tlg) - - if (length(rows_sel) > 0 && nzchar(reason) && (nca_checked || tlg_checked)) { - type_label <- if (nca_checked && tlg_checked) "NCA + TLG" - else if (nca_checked) "NCA" - else "TLG" - log_info( - "Exclusion added: ", length(rows_sel), " rows, type=", type_label, - ", reason='", reason, "'" - ) - } - .handle_add_exclusion( input, session, exclusion_list, xbtn_counter ) diff --git a/inst/shiny/modules/tab_nca/setup/settings.R b/inst/shiny/modules/tab_nca/setup/settings.R index 0b4d39fc1..912da6404 100644 --- a/inst/shiny/modules/tab_nca/setup/settings.R +++ b/inst/shiny/modules/tab_nca/setup/settings.R @@ -258,6 +258,10 @@ settings_server <- function(id, data, adnca_data, settings_override) { # A guard flag prevents infinite observer loops. updating_filters <- reactiveVal(FALSE) + # Guard to suppress log noise during initial data load / settings restore. + # Set to TRUE after the first analyte cascade completes. + filters_initialized <- reactiveVal(FALSE) + # settings_override is consumed once during the first cascade after # settings upload. After that, pending_settings is set to NULL so # subsequent user-driven changes are not overridden by stale values. @@ -288,9 +292,6 @@ settings_server <- function(id, data, adnca_data, settings_override) { available_choices = profile_choices, override_val = settings$profile ) - if (length(target_profile) > 0 && !anyNA(target_profile)) { - log_info("NCA profile selection changed: ", paste(target_profile, collapse = ", ")) - } updatePickerInput( session, "select_profile", choices = profile_choices, selected = target_profile @@ -302,9 +303,14 @@ settings_server <- function(id, data, adnca_data, settings_override) { req(data(), input$select_analyte) if (updating_filters()) return() updating_filters(TRUE) - on.exit(updating_filters(FALSE)) + on.exit({ + updating_filters(FALSE) + if (!filters_initialized()) filters_initialized(TRUE) + }) - log_info("Analyte selection changed: ", paste(input$select_analyte, collapse = ", ")) + if (filters_initialized()) { + log_info("Analyte selection changed: ", paste(input$select_analyte, collapse = ", ")) + } settings <- .consume_settings() @@ -340,7 +346,9 @@ settings_server <- function(id, data, adnca_data, settings_override) { updating_filters(TRUE) on.exit(updating_filters(FALSE)) - log_info("Specimen selection changed: ", paste(input$select_pcspec, collapse = ", ")) + if (filters_initialized()) { + log_info("Specimen selection changed: ", paste(input$select_pcspec, collapse = ", ")) + } settings <- .consume_settings() @@ -363,7 +371,13 @@ settings_server <- function(id, data, adnca_data, settings_override) { .update_profile(settings) }) - # Log method and min half-life points changes + # Log profile, method, and min half-life points changes + observeEvent(input$select_profile, { + if (filters_initialized()) { + log_info("NCA profile changed: ", paste(input$select_profile, collapse = ", ")) + } + }, ignoreInit = TRUE) + observeEvent(input$method, { log_info("Extrapolation method changed: ", input$method) }, ignoreInit = TRUE) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index dbef48589..88deb6b46 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -275,7 +275,7 @@ slope_selector_server <- function( # nolint n_rules <- nrow(manual_slopes()) n_excl <- sum(manual_slopes()$TYPE == "Exclusion", na.rm = TRUE) n_incl <- n_rules - n_excl - log_info("Slope rules updated: ", n_rules, " total (", n_incl, " inclusions, ", n_excl, " exclusions)") + log_info("Slope rules updated: ", n_rules, " total (", n_incl, " selections, ", n_excl, " exclusions)") # Update reactable with rules reactable::updateReactable( From 07a2d6e952ebb0448a28283f40a2a72d4955886d Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 24 Apr 2026 12:30:12 +0000 Subject: [PATCH 21/23] fix: consistent paste-style logging and init guards - Convert remaining glue-style log calls to paste-style (parameter_selection.R log_info, manual_slopes_table.R log_trace) - Add filters_initialized guard to method and min_hl_points observers to suppress logs during settings restore Co-authored-by: Ona --- inst/shiny/modules/tab_nca/setup/manual_slopes_table.R | 2 +- inst/shiny/modules/tab_nca/setup/parameter_selection.R | 2 +- inst/shiny/modules/tab_nca/setup/settings.R | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index c70de323f..b7f0c8f1a 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -138,7 +138,7 @@ manual_slopes_table_server <- function( # Render the manual slopes table (reactable) output$manual_slopes <- renderReactable({ req(manual_slopes()) - log_trace("{id}: rendering slope edit data table") + log_trace(id, ": rendering slope edit data table") isolate({ data <- manual_slopes() }) diff --git a/inst/shiny/modules/tab_nca/setup/parameter_selection.R b/inst/shiny/modules/tab_nca/setup/parameter_selection.R index 5b9b8df80..994b91908 100644 --- a/inst/shiny/modules/tab_nca/setup/parameter_selection.R +++ b/inst/shiny/modules/tab_nca/setup/parameter_selection.R @@ -286,7 +286,7 @@ parameter_selection_server <- function(id, processed_pknca_data, parameter_overr params <- selection_debounce() n_params <- if (is.null(params)) 0 else length(params) - log_info("Parameter selection for '{study_type}': {n_params} parameters selected.") + log_info("Parameter selection for '", study_type, "': ", n_params, " parameters selected.") if (n_params > 0) { log_debug("Parameters for '", study_type, "': ", paste(params, collapse = ", ")) } diff --git a/inst/shiny/modules/tab_nca/setup/settings.R b/inst/shiny/modules/tab_nca/setup/settings.R index 912da6404..a6fe79ba4 100644 --- a/inst/shiny/modules/tab_nca/setup/settings.R +++ b/inst/shiny/modules/tab_nca/setup/settings.R @@ -379,11 +379,15 @@ settings_server <- function(id, data, adnca_data, settings_override) { }, ignoreInit = TRUE) observeEvent(input$method, { - log_info("Extrapolation method changed: ", input$method) + if (filters_initialized()) { + log_info("Extrapolation method changed: ", input$method) + } }, ignoreInit = TRUE) observeEvent(input$min_hl_points, { - log_info("Min. half-life points changed: ", input$min_hl_points) + if (filters_initialized()) { + log_info("Min. half-life points changed: ", input$min_hl_points) + } }, ignoreInit = TRUE) # Include keyboard limits for the settings GUI display From 0d78d9506f64a8815b5d9e430757b04dd9d8ce69 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 27 Apr 2026 09:12:53 +0000 Subject: [PATCH 22/23] fix: suppress slope log on programmatic updates Add user_changed_slopes flag to only log slope rule changes triggered by user actions (plotly clicks, add/remove buttons), not by settings restore or initial render. Co-authored-by: Ona --- .../modules/tab_nca/setup/slope_selector.R | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 88deb6b46..5c9f2a05c 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -246,6 +246,9 @@ slope_selector_server <- function( # nolint manual_slopes <- slopes_table$manual_slopes refresh_reactable <- slopes_table$refresh_reactable + # Track user-initiated slope changes (buttons, plotly clicks) + user_changed_slopes <- reactiveVal(FALSE) + # Define the click events for the point exclusion and selection in the slope plots last_click_data <- reactiveVal(NULL) observeEvent(event_data("plotly_click", priority = "event"), { @@ -259,6 +262,7 @@ slope_selector_server <- function( # nolint ) # Update reactive values: last click & manual slopes table last_click_data(click_result$last_click_data) + user_changed_slopes(TRUE) manual_slopes(click_result$manual_slopes) # render rectable anew # @@ -266,16 +270,24 @@ slope_selector_server <- function( # nolint refresh_reactable(refresh_reactable() + 1) }) + # Also flag user-initiated changes from add/remove buttons + observeEvent(slopes_table$refresh_reactable(), { + user_changed_slopes(TRUE) + }, ignoreInit = TRUE) + #' Separate event handling updating displayed reactable upon every change (adding and removing #' rows, plots selection, edits). This needs to be separate call, since simply re-rendering #' the table would mean losing focus on text inputs when entering values. observeEvent(manual_slopes(), { req(manual_slopes()) - n_rules <- nrow(manual_slopes()) - n_excl <- sum(manual_slopes()$TYPE == "Exclusion", na.rm = TRUE) - n_incl <- n_rules - n_excl - log_info("Slope rules updated: ", n_rules, " total (", n_incl, " selections, ", n_excl, " exclusions)") + if (user_changed_slopes()) { + n_rules <- nrow(manual_slopes()) + n_excl <- sum(manual_slopes()$TYPE == "Exclusion", na.rm = TRUE) + n_incl <- n_rules - n_excl + log_info("Slope rules updated: ", n_rules, " total (", n_incl, " selections, ", n_excl, " exclusions)") + user_changed_slopes(FALSE) + } # Update reactable with rules reactable::updateReactable( From c0fe8c31bdbddbe4698c3dcca57040ef3323742c Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 30 Apr 2026 14:38:14 +0000 Subject: [PATCH 23/23] fix: remove stale logger import and dead sub-module code - Remove @importFrom logger log_info from imports-shiny.R and NAMESPACE (logger is no longer used, log_info is now in logging.R) - Remove logger from DESCRIPTION Suggests (no remaining references) - Remove dead observeEvent(study_types_list()) block from parameter_selection.R that referenced the old sub-module approach (param_selector_panel_server) which no longer exists in main - Replace with debounced observer on selections_state() compatible with main's matrix-based parameter selection UI - Update session_log.Rmd to remove DEBUG parameter list entry Co-authored-by: Ona --- DESCRIPTION | 1 - NAMESPACE | 2 +- R/imports-shiny.R | 1 - .../tab_nca/setup/parameter_selection.R | 84 +++---------------- vignettes/session_log.Rmd | 1 - 5 files changed, 12 insertions(+), 77 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3ea2758d7..345d198e7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -66,7 +66,6 @@ Suggests: jsonlite, knitr, lintr (>= 3.2.0), - logger, markdown, mockery, nestcolor, diff --git a/NAMESPACE b/NAMESPACE index 55e4b9e59..03dacd0f5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -143,7 +143,7 @@ importFrom(glue,glue) importFrom(grid,convertUnit) importFrom(htmltools,tags) importFrom(htmlwidgets,onRender) -importFrom(logger,log_info) + importFrom(magrittr,`%>%`) importFrom(plotly,add_lines) importFrom(plotly,add_trace) diff --git a/R/imports-shiny.R b/R/imports-shiny.R index 119a2bfa7..2f6e36b92 100644 --- a/R/imports-shiny.R +++ b/R/imports-shiny.R @@ -7,7 +7,6 @@ #' @importFrom bslib page_sidebar #' @importFrom htmltools tags #' @importFrom htmlwidgets onRender -#' @importFrom logger log_info #' @importFrom reactable reactable #' @importFrom reactable.extras reactable_extras_dependency #' @importFrom shiny runApp diff --git a/inst/shiny/modules/tab_nca/setup/parameter_selection.R b/inst/shiny/modules/tab_nca/setup/parameter_selection.R index 51b7e21b0..eb7cb590e 100644 --- a/inst/shiny/modules/tab_nca/setup/parameter_selection.R +++ b/inst/shiny/modules/tab_nca/setup/parameter_selection.R @@ -317,79 +317,17 @@ parameter_selection_server <- function(id, processed_pknca_data, parameter_overr ) }) - observeEvent(study_types_list(), { - req(selections_state()) - - current_types <- study_types_list() - - # Define grouped structure of parameters for the sub-module - all_params_grouped <- all_params %>% - # Split the data frame into a list of data frames, one per TYPE - split(.$TYPE) - - # Loop and create servers - map(current_types, function(study_type) { - # Define module ID - module_id <- gsub("[^A-Za-z0-9]", "_", study_type) - - if (exists(module_id, envir = initialised_modules)) { - # If it exists, exit this iteration - return() - } - - # If not, mark it as initialized - assign(module_id, TRUE, envir = initialised_modules) - - current_type_selections <- reactive({ - state <- selections_state() - req(state) - # Return vector of PKNCA codes where this study type is TRUE - state$PKNCA[state[[study_type]] == TRUE] - }) - - # Call the parameter selector panel server - selection_single_type_grouped <- param_selector_panel_server( - module_id, - all_params_grouped = all_params_grouped, - current_selection = current_type_selections - ) - - # Watch the module's return value - observeEvent(selection_single_type_grouped(), { - # Get the full master state - selections_df <- selections_state() - - # Get the selected names from the module - selected_params <- selection_single_type_grouped() - - # If the input is NULL (empty selection), treat it as an empty character vector - if (is.null(selected_params)) { - selected_params <- character(0) - } - - # We compare the current state to the new state to avoid infinite loops - current_col <- selections_df[[study_type]] - new_col <- selections_df$PKNCA %in% selected_params - - if (!identical(current_col, new_col)) { - selections_df[[study_type]] <- new_col - selections_state(selections_df) - } - }, ignoreNULL = FALSE, ignoreInit = TRUE) - - selection_debounce <- debounce(selection_single_type_grouped, 2000) - - observeEvent(selection_debounce(), { - params <- selection_debounce() - n_params <- if (is.null(params)) 0 else length(params) - - log_info("Parameter selection for '", study_type, "': ", n_params, " parameters selected.") - if (n_params > 0) { - log_debug("Parameters for '", study_type, "': ", paste(params, collapse = ", ")) - } - }, ignoreNULL = FALSE, ignoreInit = TRUE) - }) - }) + # Log parameter selection changes (debounced to avoid noise from rapid clicks) + selections_debounced <- debounce(selections_state, 2000) + observeEvent(selections_debounced(), { + state <- selections_debounced() + req(state) + study_types <- study_types_list() + for (st in study_types) { + n_params <- sum(state[[st]], na.rm = TRUE) + log_info("Parameter selection for '", st, "': ", n_params, " parameters selected.") + } + }, ignoreInit = TRUE) observeEvent(input$clear_all, { state <- selections_state() diff --git a/vignettes/session_log.Rmd b/vignettes/session_log.Rmd index 183575b3f..7e0d08546 100644 --- a/vignettes/session_log.Rmd +++ b/vignettes/session_log.Rmd @@ -115,7 +115,6 @@ organized by the stage of the workflow where they occur. | Event | Level | |---|---| | Parameter count per study type | INFO | -| Full parameter list per study type | DEBUG | ### Slope Selector