-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhooks.R
162 lines (152 loc) · 5.16 KB
/
hooks.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#' Injects a logger call to standard messages
#'
#' This function uses `trace` to add a `log_info` function call when
#' `message` is called to log the informative messages with the
#' `logger` layout and appender.
#' @export
#' @examples \dontrun{
#' log_messages()
#' message("hi there")
#' }
log_messages <- function() {
if (any(sapply(
globalCallingHandlers()[names(globalCallingHandlers()) == "message"],
attr,
which = "implements"
) == "log_messages")) {
warning("Ignoring this call to log_messages as it was registered previously.")
} else {
globalCallingHandlers(
message = structure(function(m) {
logger::log_level(logger::INFO, m$message, .topcall = m$call)
}, implements = "log_messages")
)
}
}
#' Injects a logger call to standard warnings
#'
#' This function uses `trace` to add a `log_warn` function call when
#' `warning` is called to log the warning messages with the `logger`
#' layout and appender.
#' @param muffle if TRUE, the warning is not shown after being logged
#' @export
#' @examples \dontrun{
#' log_warnings()
#' for (i in 1:5) {
#' Sys.sleep(runif(1))
#' warning(i)
#' }
#' }
log_warnings <- function(muffle = getOption("logger_muffle_warnings", FALSE)) {
if (any(sapply(
globalCallingHandlers()[names(globalCallingHandlers()) == "warning"],
attr,
which = "implements"
) == "log_warnings")) {
warning("Ignoring this call to log_warnings as it was registered previously.")
} else {
globalCallingHandlers(
warning = structure(function(m) {
logger::log_level(logger::WARN, m$message, .topcall = m$call)
if (isTRUE(muffle)) {
invokeRestart("muffleWarning")
}
}, implements = "log_warnings")
)
}
}
#' Injects a logger call to standard errors
#'
#' This function uses `trace` to add a `log_error` function call when
#' `stop` is called to log the error messages with the `logger` layout
#' and appender.
#' @param muffle if TRUE, the error is not thrown after being logged
#' @export
#' @examples \dontrun{
#' log_errors()
#' stop("foobar")
#' }
log_errors <- function(muffle = getOption("logger_muffle_errors", FALSE)) {
if (any(sapply(
globalCallingHandlers()[names(globalCallingHandlers()) == "error"],
attr,
which = "implements"
) == "log_errors")) {
warning("Ignoring this call to log_errors as it was registered previously.")
} else {
globalCallingHandlers(
error = structure(function(m) {
logger::log_level(logger::ERROR, m$message, .topcall = m$call)
if (isTRUE(muffle)) {
invokeRestart("abort")
}
}, implements = "log_errors")
)
}
}
#' Auto logging input changes in Shiny app
#'
#' This is to be called in the `server` section of the Shiny app.
#' @export
#' @param input passed from Shiny's `server`
#' @param level log level
#' @param excluded_inputs character vector of input names to exclude from logging
#' @param namespace the name of the namespace
#' @importFrom utils assignInMyNamespace assignInNamespace
#' @examples \dontrun{
#' library(shiny)
#'
#' ui <- bootstrapPage(
#' numericInput("mean", "mean", 0),
#' numericInput("sd", "sd", 1),
#' textInput("title", "title", "title"),
#' textInput("foo", "This is not used at all, still gets logged", "foo"),
#' passwordInput("password", "Password not to be logged", "secret"),
#' plotOutput("plot")
#' )
#'
#' server <- function(input, output) {
#' logger::log_shiny_input_changes(input, excluded_inputs = "password")
#'
#' output$plot <- renderPlot({
#' hist(rnorm(1e3, input$mean, input$sd), main = input$title)
#' })
#' }
#'
#' shinyApp(ui = ui, server = server)
#' }
log_shiny_input_changes <- function(input,
level = INFO,
namespace = NA_character_,
excluded_inputs = character()) {
fail_on_missing_package("shiny")
fail_on_missing_package("jsonlite")
session <- shiny::getDefaultReactiveDomain()
ns <- ifelse(!is.null(session), session$ns(character(0)), "")
if (!(shiny::isRunning() || inherits(session, "MockShinySession") || inherits(session, "session_proxy"))) {
stop("No Shiny app running, it makes no sense to call this function outside of a Shiny app")
}
input_values <- shiny::isolate(shiny::reactiveValuesToList(input))
assignInMyNamespace("shiny_input_values", input_values)
log_level(level, skip_formatter(trimws(paste(
ns,
"Default Shiny inputs initialized:",
as.character(jsonlite::toJSON(input_values, auto_unbox = TRUE))
))), namespace = namespace)
shiny::observe({
old_input_values <- shiny_input_values
new_input_values <- shiny::reactiveValuesToList(input)
names <- unique(c(names(old_input_values), names(new_input_values)))
names <- setdiff(names, excluded_inputs)
for (name in names) {
old <- old_input_values[name]
new <- new_input_values[name]
if (!identical(old, new)) {
message <- trimws("{ns} Shiny input change detected in {name}: {old} -> {new}")
log_level(level, message, namespace = namespace)
}
}
assignInNamespace("shiny_input_values", new_input_values, ns = "logger")
})
}
shiny_input_values <- NULL