Skip to content

Commit 9fe2764

Browse files
committed
Refactor LayoutJson and improve documentation & small behaviour improvements
1 parent d9ef09c commit 9fe2764

File tree

3 files changed

+241
-125
lines changed

3 files changed

+241
-125
lines changed

R/Layout.R

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -322,19 +322,3 @@ LayoutGlue <- R6::R6Class(
322322
.fmt = NULL
323323
)
324324
)
325-
326-
327-
328-
329-
330-
331-
332-
# utils -------------------------------------------------------------------
333-
334-
fmt_timestamp = function(x, fmt){
335-
if (is.character(fmt)){
336-
format(x, fmt)
337-
} else if (is.function(fmt)){
338-
fmt(x)
339-
}
340-
}

R/LayoutJson.R

Lines changed: 140 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,6 @@
77
#' [jsonlines](https://jsonlines.org/) log files. This provides a
88
#' nice balance between human- an machine-readability.
99
#'
10-
#'
11-
#' @section Event transformation:
12-
#' This Layout provides 4 ways to transform the event before serialization:
13-
#'
14-
#' 1. `transform_event`: a generic function to transform the event
15-
#' 2. `timestamp_fmt`: a format string or function to apply to the timestamp field
16-
#' 3. `transform_event_names`: a named `character` vector or a second
17-
#' function to rename fields
18-
#' 4. `excluded_fields`: a `character` vector to include fields.
19-
#'
20-
#' In theory supplying a custom `transform_event` function is enough to
21-
#' perform all these actions, but the other three parameters are provided for
22-
#' convenience. Please note that they are applied in order (e.g. if you
23-
#' rename a field you have to exclude the *renamed* field to really exclude it).
24-
#'
2510
#' @family Layouts
2611
#' @seealso [read_json_lines()], [https://jsonlines.org/](https://jsonlines.org/)
2712
#' @export
@@ -36,9 +21,25 @@
3621
#' custom_field = "LayoutJson can handle arbitrary fields"
3722
#' )
3823
#'
39-
#' # Default settings show all event fals
4024
#' lo <- LayoutJson$new()
4125
#' lo$format_event(event)
26+
#'
27+
#' lo <- LayoutJson$new(
28+
#' transform_event_names = toupper,
29+
#' excluded_fields = c("RAWMSG", "CALLER"))
30+
#'
31+
#' lo$format_event(event)
32+
#'
33+
#' lo <- LayoutJson$new(
34+
#' transform_event = function(e) {
35+
#' values <- e$values
36+
#' values$msg <- toupper(values$msg)
37+
#' values
38+
#' },
39+
#' timestamp_fmt = "%a %b %d %H:%M:%S %Y",
40+
#' excluded_fields = c("RAWMSG", "CALLER"))
41+
#'
42+
#' lo$format_event(event)
4243
LayoutJson <- R6::R6Class(
4344
"LayoutJson",
4445
inherit = Layout,
@@ -49,30 +50,31 @@ LayoutJson <- R6::R6Class(
4950
#'
5051
#' @param toJSON_args a list of arguments passed to [jsonlite::toJSON()],
5152
#'
52-
#' @param timestamp_fmt Format to be applied to the timestamp. This is
53-
#' applied after `transform_event()` but `before transform_event_names()`
54-
#' * `NULL` (the default): formatting of the timestamp is left to
55-
#' [jsonlite::toJSON()],
56-
#' * a `character` scalar as for [format.POSIXct()], or
57-
#' * a `function` that returns a vector of the same length as its
58-
#' ([POSIXct]) input. The returned vector can be of any type
59-
#' supported by [jsonlite::toJSON()], but should usually be `character`.
53+
#' @param transform_event a `function` with a single argument that
54+
#' takes a [LogEvent] object and returns a `list` of values.
6055
#'
61-
#' @param transform_event a `function` with a single argument `event` that
62-
#' takes a [LogEvent] object and returns a list of values.
56+
#' @param timestamp_fmt Format to be applied to the timestamp. This is
57+
#' applied after `transform_event` but `before transform_event_names`
58+
#' * `NULL`: formatting of the timestamp is left to [jsonlite::toJSON()],
59+
#' * a `character` scalar as for [format.POSIXct()], or
60+
#' * a `function` that returns a vector of the same length as its
61+
#' ([POSIXct]) input. The returned vector can be of any type
62+
#' supported by [jsonlite::toJSON()].
6363
#'
64-
#' @param transform_event_names A named `character` vector mapping original
65-
#' field names to Dynatrace-compatible ones, or a function with a single
66-
#' mandatory argument that accepts a character vector of field names.
67-
#' Applied after to `transform_event()`.
64+
#' @param transform_event_names
65+
#' * `NULL`: don't process names
66+
#' * a named `character` vector where the names are the original field names
67+
#' and the values the desired new field names,
68+
#' * or a `function` with a single mandatory argument that accepts a
69+
#' `character` vector of field names. Applied after `transform_event`.
6870
#'
6971
#' @param excluded_fields A `character` vector of field names to exclude
7072
#' from the final output. Applied after `transform_event_names`.
7173
initialize = function(
7274
toJSON_args = list(auto_unbox = TRUE),
7375
timestamp_fmt = NULL,
7476
transform_event = function(event) event[["values"]],
75-
transform_event_names = identity,
77+
transform_event_names = NULL,
7678
excluded_fields = "rawMsg"
7779
){
7880
self$set_toJSON_args(toJSON_args)
@@ -83,115 +85,125 @@ LayoutJson <- R6::R6Class(
8385
},
8486

8587
format_event = function(event) {
86-
8788
values <- get(".transform_event", private)(event)
88-
89-
if (!is.null(self[["timestamp_fmt"]])){
90-
values[["timestamp"]] <- fmt_timestamp(values[["timestamp"]], self[["timestamp_fmt"]])
91-
}
92-
93-
if (is.character(self$transform_event_names)){
94-
original_names <- names(values)
95-
rename_idx <- match(original_names, names(self$transform_event_names), nomatch = 0L)
96-
names(values)[rename_idx > 0L] <- self$transform_event_names[rename_idx[rename_idx > 0L]]
97-
98-
} else if (is.function(self$transform_event_names)){
99-
names(values) <- self$transform_event_names(names(values))
100-
101-
} else {
102-
warning("`transform_event_names` must be a character vector or a function")
103-
}
104-
105-
if (!is.null(self$excluded_fields)) {
106-
values <- values[!names(values) %in% self$excluded_fields]
107-
}
89+
values[["timestamp"]] <- apply_timestamp_formatter(values[["timestamp"]], get(".timestamp_fmt", private))
90+
names(values) <- apply_event_name_transformer(names(values), get(".transform_event_names", private))
91+
values <- apply_field_exclusion(values, self$excluded_fields)
10892

10993
do.call(
11094
jsonlite::toJSON,
11195
args = c(list(x = values), get(".toJSON_args", private))
11296
)
11397
},
11498

99+
# . . setters -------------------------------------------------------------
100+
101+
#' @param x a `list`
115102
set_toJSON_args = function(x){
116103
assert(is.list(x))
117104
assert(identical(length(names(x)), length(x)))
118105
private$.toJSON_args <- x
119106
invisible(self)
120107
},
121108

122-
123-
# . . setters -------------------------------------------------------------
124-
109+
#' @param x a `character` scalar or a `function` that accepts a `POSIXct`
110+
#' as its single argument
125111
set_timestamp_fmt = function(x){
126112
assert(is.null(x) || is_scalar_character(x) || is.function(x))
127113
private[[".timestamp_fmt"]] <- x
128114
invisible(self)
129115
},
130116

117+
#' @param x a `function` that accepts a `LogEvent` as its single argument
131118
set_transform_event = function(x){
132119
assert(
133-
is.function(x) &&
134-
identical(names(formals(x)), "event"),
135-
"`transform_event` must be a function a single argument `event`")
120+
is.function(x) && length(formals(x)) >= 1L,
121+
"`transform_event` must be a function a single argument (optional arguments are OK)")
136122

137123
private[[".transform_event"]] <- x
138124
invisible(self)
139125
},
140126

127+
#' @param x a named `character` vector or a function that accepts a
128+
#' `character` vector of field names as its single argument.
141129
set_transform_event_names = function(x){
142-
assert(is.function(x) || is_field_name_map(x),
143-
"`transform_event_names` must be a named character vector or function with a single mandatory argument (optional arguments are OK)")
130+
assert(
131+
is.null(x) || is_field_name_map(x) || (is.function(x) && length(formals(x)) >= 1L),
132+
"`transform_event_names` must be a named character vector or function with a single mandatory argument (optional arguments are OK)")
144133

145134
private[[".transform_event_names"]] <- x
146135
},
147136

148137
# . . methods ----------------------------------------------------------------
149138

139+
#' @description Represent the `LayoutJson` class as a string
150140
toString = function() {
151141
fmt_class(class(self)[[1]])
152142
},
153143

154-
parse = function(
155-
file
156-
){
157-
read_json_lines(file)
158-
},
144+
#' @description Read and parse file written using this Layout
145+
#'
146+
#' This can be used by the `$data` active binding of an [Appender]
147+
#'
148+
#' @param file `character` scalar: path to a file
149+
parse = function(file){
150+
read_json_lines(file)
151+
},
159152

153+
#' @description Read a file written using this Layout (without parsing)
154+
#'
155+
#' This can be used by the `$show()` method of an [Appender]
156+
#'
157+
#' @param file `character` scalar: path to a file
158+
#' @param threshold `character` Minimum log level to show. Requires parsing
159+
#' of the log file (but will still display unparsed output)
160+
#' @param n `integer` number of lines to show
160161
read = function(
161162
file,
162163
threshold = NA_integer_,
163164
n = 20L
164-
){
165-
assert(is_scalar_integerish(n))
166-
threshold <- standardize_threshold(threshold)
167-
168-
dd <- readLines(file)
169-
if (!is.na(threshold)){
170-
sel <- self$parse(file)$level <= threshold
171-
} else {
172-
sel <- TRUE
173-
}
174-
175-
dd <- tail(dd[sel], n)
176-
dd
165+
){
166+
assert(is_scalar_integerish(n))
167+
threshold <- standardize_threshold(threshold)
168+
169+
dd <- readLines(file)
170+
if (!is.na(threshold)){
171+
sel <- self$parse(file)$level <= threshold
172+
} else {
173+
sel <- TRUE
177174
}
175+
176+
dd <- tail(dd[sel], n)
177+
dd
178+
}
178179
),
179180

180181

181182
# . . active fields ------------------------------------------------------
182183

183184
active = list(
185+
186+
#' @field toJSON_args a `list`
184187
toJSON_args = function() {
185188
get(".toJSON_args", private)
186189
},
187190

191+
#' @field timestamp_fmt a `character` scalar or a `function` that accepts a `POSIXct`
192+
#' as its single argument
188193
timestamp_fmt = function() {
189194
get(".timestamp_fmt", private)
190195
},
191196

192-
transform_event = function() get(".transform_event", private),
197+
#' @field transform_event a `function` that accepts a `LogEvent` as its single argument
198+
transform_event = function(){
199+
get(".transform_event", private)
200+
},
193201

194-
transform_event_names = function() get(".transform_event_names", private)
202+
#' @field transform_event_names a named `character` vector or a function that accepts a
203+
#' `character` vector of field names as its single argument.
204+
transform_event_names = function() {
205+
get(".transform_event_names", private)
206+
}
195207
),
196208

197209
# . . private --------------------------------------------------------------
@@ -204,6 +216,54 @@ LayoutJson <- R6::R6Class(
204216
)
205217

206218

219+
# utils -------------------------------------------------------------------
220+
221+
apply_timestamp_formatter = function(x, f){
222+
if (is.null(f)){
223+
return(x)
224+
}
225+
226+
if (is.character(f)){
227+
return(format(x, f))
228+
}
229+
230+
if (is.function(f)){
231+
return(f(x))
232+
}
233+
234+
warning("`f` must be a character scalar or a function")
235+
}
236+
237+
238+
apply_event_name_transformer = function(x, f){
239+
if (is.null(f)){
240+
return(x)
241+
}
242+
243+
if (is.character(f)){
244+
rename_idx <- match(x, names(f), nomatch = 0L)
245+
x[rename_idx > 0L] <- f[rename_idx[rename_idx > 0L]]
246+
return(x)
247+
}
248+
249+
if(is.function(f)){
250+
return(f(x))
251+
}
252+
253+
warning("`f` must be a named character vector or a function")
254+
x
255+
}
256+
257+
258+
apply_field_exclusion <- function(x, f){
259+
if (is.null(f)){
260+
return(x)
261+
}
262+
263+
x[!names(x) %in% f]
264+
}
265+
266+
207267
is_field_name_map <- function(x){
208268
is.character(x) && !is.null(names(x)) && all(nzchar(names(x)))
209269
}

0 commit comments

Comments
 (0)