Skip to content

Commit 572fc75

Browse files
committed
Handle null values and empty vectors
1 parent 51b8bb1 commit 572fc75

File tree

4 files changed

+169
-19
lines changed

4 files changed

+169
-19
lines changed

NEWS.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
`sprintf()` or `glue()`). rawMessage will be added by default to json
1010
log files (#60)
1111

12-
* updated Readme
12+
* Replace `NULL` values and empty characters in logging by the string `"<NULL>"`.
13+
Before, `NULL` values would have resulted in empty log messages. (#51)
14+
15+
* Support transformers for `LoggerGlue` (see `?glue::glue`) (#51)
16+
17+
* updated README
1318

1419

1520
# lgr 0.4.4

R/Logger.R

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
NULL
106106

107107

108-
108+
# Logger ------------------------------------------------------------------
109109

110110
#' @export
111111
Logger <- R6::R6Class(
@@ -148,7 +148,8 @@ Logger <- R6::R6Class(
148148
threshold = NULL,
149149
filters = list(),
150150
exception_handler = default_exception_handler,
151-
propagate = TRUE
151+
propagate = TRUE,
152+
replace_empty = "<NULL>"
152153
){
153154
# fields
154155
# threshold must be set *after* the logging functions have been initalized
@@ -167,6 +168,7 @@ Logger <- R6::R6Class(
167168
self$set_propagate(propagate)
168169
self$set_filters(filters)
169170
self$set_exception_handler(exception_handler)
171+
self$set_replace_empty(replace_empty)
170172

171173
invisible(self)
172174
},
@@ -241,7 +243,9 @@ Logger <- R6::R6Class(
241243
dots <- list(...)
242244

243245
if (is.null(names(dots))){
244-
msg <- sprintf(msg, ...)
246+
dots <- replace_empty(dots, get("replace_empty", self))
247+
248+
msg <- do.call(sprintf, args = c(list(msg), dots))
245249
vals <- list(
246250
logger = self,
247251
level = level,
@@ -252,8 +256,11 @@ Logger <- R6::R6Class(
252256
)
253257
} else {
254258
not_named <- vapply(names(dots), is_blank, TRUE, USE.NAMES = FALSE)
259+
unnamed_dots <- dots[not_named]
260+
unnamed_dots <- replace_empty(unnamed_dots, get("replace_empty", self))
261+
255262
if (any(not_named)){
256-
msg <- do.call(sprintf, c(list(msg), dots[not_named]))
263+
msg <- do.call(sprintf, c(list(msg), unnamed_dots))
257264
}
258265

259266
vals <- c(
@@ -559,6 +566,8 @@ Logger <- R6::R6Class(
559566
},
560567

561568

569+
# .. setters --------------------------------------------------------------
570+
562571
#' @description Set the exception handler of a logger
563572
#'
564573
#' @param fun a `function` with the single argument `e` (an error [condition])
@@ -611,6 +620,17 @@ Logger <- R6::R6Class(
611620
invisible(self)
612621
},
613622

623+
#' @description Set the replacement for empty values (`NULL` or empty
624+
#' vectors)
625+
#'
626+
#' @param x should be a `character` vector, but other types of values are
627+
#' supported. use wisely.
628+
set_replace_empty = function(x){
629+
private[[".replace_empty"]] <- x
630+
631+
invisible(self)
632+
},
633+
614634

615635
#' @description Spawn a child Logger.
616636
#' This is very similar to using [get_logger()], but
@@ -625,7 +645,7 @@ Logger <- R6::R6Class(
625645
),
626646

627647

628-
# active bindings ---------------------------------------------------------
648+
# .. active bindings ---------------------------------------------------------
629649
active = list(
630650

631651
#' @field name A `character` scalar. The unique name of each logger,
@@ -716,6 +736,9 @@ Logger <- R6::R6Class(
716736
}
717737
},
718738

739+
replace_empty = function() {
740+
get(".replace_empty", envir = private)
741+
},
719742

720743
#' @field exception_handler a `function`. See `$set_exception_handler` and
721744
#' `$handle_exception`
@@ -725,7 +748,7 @@ Logger <- R6::R6Class(
725748
),
726749

727750

728-
# private -----------------------------------------------------------------
751+
# .. private -----------------------------------------------------------------
729752
private = list(
730753
set_name = function(x){
731754
assert(is_scalar_character(x))
@@ -747,13 +770,13 @@ Logger <- R6::R6Class(
747770
invisible()
748771
},
749772

750-
# +- fields ---------------------------------------------------------------
751773
.propagate = NULL,
752774
.exception_handler = NULL,
753775
.name = NULL,
754776
.appenders = NULL,
755777
.threshold = NULL,
756-
.last_event = NULL
778+
.last_event = NULL,
779+
.replace_empty = NULL
757780
)
758781
)
759782

@@ -784,6 +807,40 @@ LoggerGlue <- R6::R6Class(
784807

785808
public = list(
786809

810+
initialize = function(
811+
name = "(unnamed logger)",
812+
appenders = list(),
813+
threshold = NULL,
814+
filters = list(),
815+
exception_handler = default_exception_handler,
816+
propagate = TRUE,
817+
replace_empty = "<NULL>",
818+
transformer = NULL
819+
){
820+
# fields
821+
# threshold must be set *after* the logging functions have been initalized
822+
if (identical(name, "(unnamed logger)")){
823+
warning(
824+
"When creating a new Logger, you should assign it a unique `name`. ",
825+
"Please see ?Logger for more infos.", call. = FALSE
826+
)
827+
}
828+
829+
private$set_name(name)
830+
private$.last_event <- LogEvent$new(self)
831+
832+
self$set_threshold(threshold)
833+
self$set_appenders(appenders)
834+
self$set_propagate(propagate)
835+
self$set_filters(filters)
836+
self$set_exception_handler(exception_handler)
837+
self$set_replace_empty(replace_empty)
838+
self$set_transformer(transformer)
839+
840+
invisible(self)
841+
},
842+
843+
# .. methods --------------------------------------------------------------
787844
fatal = function(..., caller = get_caller(-8L), .envir = parent.frame()){
788845
if (isTRUE(get("threshold", envir = self) < 100L))
789846
return(invisible())
@@ -918,8 +975,18 @@ LoggerGlue <- R6::R6Class(
918975
}
919976
})
920977

978+
dots_msg <- replace_empty(dots_msg, get("replace_empty", self))
921979
rawMsg <- dots[[1]]
922-
msg <- do.call(glue::glue, args = c(dots_msg, list(.envir = .envir)))
980+
981+
glue_args <- list(.envir = .envir)
982+
983+
transformer <- get("transformer", self)
984+
985+
if (!is.null(transformer)){
986+
glue_args[[".transformer"]] <- transformer
987+
}
988+
989+
msg <- do.call(glue::glue, args = c(dots_msg, glue_args))
923990

924991
# Check if LogEvent should be created
925992
if (
@@ -992,7 +1059,31 @@ LoggerGlue <- R6::R6Class(
9921059

9931060
spawn = function(name){
9941061
get_logger_glue(c(private[[".name"]], name))
1062+
},
1063+
1064+
# .. setters -----------------------------------------------------------------
1065+
1066+
#' @description Set the transformer for glue string interpolation
1067+
#'
1068+
#' @param x single [function] taking two arguments. See [glue::glue()].
1069+
set_transformer = function(x){
1070+
private[[".transformer"]] <- x
1071+
1072+
invisible(self)
1073+
}
1074+
),
1075+
1076+
# .. active bindings ---------------------------------------------------
1077+
active = list(
1078+
transformer = function() {
1079+
get(".transformer", envir = private)
9951080
}
1081+
),
1082+
1083+
1084+
# . private ------------------------------------
1085+
private = list(
1086+
.transformer = NULL
9961087
)
9971088
)
9981089

R/utils-sfmisc.R

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,19 @@ class_fmt <- function(x, ignore = NULL){
5858
}
5959

6060

61-
62-
6361
compact <- function(x){
64-
x[!vapply(x, is.null, FALSE)]
62+
x[!vapply(x, is_empty, FALSE)]
6563
}
6664

6765

66+
replace_empty <- function(x, fallback = "<NULL>"){
67+
for (i in seq_along(x)){
68+
if (is_empty(x[[i]])){
69+
x[[i]] <- fallback
70+
}
71+
}
72+
x
73+
}
6874

6975

7076
walk <- function(.x, .f, ...){
@@ -385,14 +391,14 @@ is_equal_length <- function(...){
385391

386392
#' Check if Object has length 0
387393
#'
388-
#' Check wheter an object is either `TRUE` or `FALSE`.
394+
#' Check whether an object is either `TRUE` or `FALSE`.
389395
#'
390396
#' @param x Any \R Object.
391397
#' @return either `TRUE` or `FALSE`
392398
#' @noRd
393399
#'
394400
is_empty <- function(x){
395-
identical(length(x), 0L)
401+
!length(x)
396402
}
397403

398404

tests/testthat/test_Logger.R

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,24 @@ test_that("$ancestry works", {
321321
})
322322

323323

324+
test_that("Logger handles null values", {
325+
l <- Logger$new("test", propagate = FALSE)
326+
327+
log_null <- l$info("foo %s %s", NULL, character())
328+
expect_equal(log_null, "foo <NULL> <NULL>")
329+
})
330+
331+
332+
test_that("Logger handles null values along with named parameters", {
333+
l <- Logger$new("test", propagate = FALSE)
334+
335+
log_null <- l$info("foo %s %s", NULL, character(), valbert = "hashbaz", nullbert = NULL)
336+
expect_equal(log_null, "foo <NULL> <NULL>")
337+
338+
expect_true(l$last_event$valbert == "hashbaz")
339+
expect_true("nullbert" %in% names(l$last_event$values))
340+
expect_null(l$last_event$nullbert)
341+
})
324342

325343

326344
# LoggerGlue --------------------------------------------------------------
@@ -347,8 +365,6 @@ test_that("LoggerGlue supports custom fields", {
347365
})
348366

349367

350-
351-
352368
test_that("LoggerGlue uses the correct evaluation environment", {
353369
l <- LoggerGlue$new("glue", propagate = FALSE)
354370

@@ -359,8 +375,6 @@ test_that("LoggerGlue uses the correct evaluation environment", {
359375
})
360376

361377

362-
363-
364378
test_that("$config works with lists", {
365379
l <- LoggerGlue$new("glue", propagate = FALSE)
366380

@@ -380,6 +394,40 @@ test_that("$config works with lists", {
380394
})
381395

382396

397+
test_that("LoggerGlue handles null values", {
398+
l <- LoggerGlue$new("glue", propagate = FALSE)
399+
400+
log_null <- l$info("foo", NULL)
401+
expect_equal(log_null, "foo<NULL>")
402+
403+
log_null_parameter <- l$info("foo {bar} {baz}", bar = NULL, baz = character())
404+
expect_equal(log_null_parameter, "foo <NULL> <NULL>")
405+
})
406+
407+
408+
test_that("LoggerGlue handles null values along with named parameters", {
409+
l <- LoggerGlue$new("test", propagate = FALSE)
410+
411+
log_null <- l$info("foo ", NULL, character(), valbert = "hashbaz", nullbert = NULL)
412+
expect_equal(log_null, "foo <NULL><NULL>")
413+
414+
expect_true(l$last_event$valbert == "hashbaz")
415+
expect_true("nullbert" %in% names(l$last_event$values))
416+
expect_null(l$last_event$nullbert)
417+
})
418+
419+
420+
test_that("Logger glue can use custom transformers", {
421+
transformer <- function(text, envir) {
422+
"transformed text"
423+
}
424+
425+
l <- LoggerGlue$new("glue", propagate = FALSE, transformer = transformer)
426+
427+
e <- log_null <- l$info("foo {bar}", foo = "bar")
428+
429+
expect_equal(e, "foo transformed text")
430+
})
383431

384432

385433
# Multi-Logger tests -------------------------------------------------------------

0 commit comments

Comments
 (0)