Skip to content

Commit edaa10b

Browse files
committed
Add excluded_fields, transform_event and
`transform_event_names` to `Layout` to make it more flexible.
1 parent 03da869 commit edaa10b

File tree

11 files changed

+502
-243
lines changed

11 files changed

+502
-243
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Collate:
5656
'log_levels.R'
5757
'LogEvent.R'
5858
'Layout.R'
59+
'LayoutJson.R'
5960
'Logger.R'
6061
'basic_config.R'
6162
'default_functions.R'

NEWS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313

1414
- Support transformers for `LoggerGlue` (see `?glue::glue`) (#51)
1515

16-
- Add support for `excluded_fields` to `Layout`, and exclude `rawMsg` by
17-
default from `LayoutJson` (for backwards compatibility)
16+
- Add `excluded_fields`, `transform_event` and
17+
`transform_event_names` to `Layout` to make it more flexible. Exclude `rawMsg`
18+
by default for backwards compatibility.
1819

1920

2021
# lgr 0.4.4

R/Layout.R

Lines changed: 0 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -326,144 +326,6 @@ LayoutGlue <- R6::R6Class(
326326

327327

328328

329-
# LayoutJson --------------------------------------------------------------
330-
331-
#' Format LogEvents as JSON
332-
#'
333-
#' @description
334-
#' A format for formatting LogEvents as
335-
#' [jsonlines](https://jsonlines.org/) log files. This provides a
336-
#' nice balance between human- an machine-readability.
337-
#'
338-
#' @family Layouts
339-
#' @seealso [read_json_lines()], [https://jsonlines.org/](https://jsonlines.org/)
340-
#' @export
341-
#' @examples
342-
#' # setup a dummy LogEvent
343-
#' event <- LogEvent$new(
344-
#' logger = Logger$new("dummy logger"),
345-
#' level = 200,
346-
#' timestamp = Sys.time(),
347-
#' caller = NA_character_,
348-
#' msg = "a test message",
349-
#' custom_field = "LayoutJson can handle arbitrary fields"
350-
#' )
351-
#'
352-
#' # Default settings show all event fals
353-
#' lo <- LayoutJson$new()
354-
#' lo$format_event(event)
355-
LayoutJson <- R6::R6Class(
356-
"LayoutJson",
357-
inherit = Layout,
358-
public = list(
359-
360-
initialize = function(
361-
toJSON_args = list(auto_unbox = TRUE),
362-
timestamp_fmt = NULL,
363-
excluded_fields = "rawMsg"
364-
){
365-
self$set_toJSON_args(toJSON_args)
366-
self$set_timestamp_fmt(timestamp_fmt)
367-
self$set_excluded_fields(excluded_fields)
368-
},
369-
370-
format_event = function(event) {
371-
vals <- get("values", event)
372-
vals <- vals[!names(vals) %in% self$excluded_fields]
373-
fmt <- get("timestamp_fmt", self)
374-
375-
if (!is.null(fmt)){
376-
vals[["timestamp"]] <- fmt_timestamp(vals[["timestamp"]], fmt)
377-
}
378-
379-
do.call(
380-
jsonlite::toJSON,
381-
args = c(list(x = vals), get(".toJSON_args", private))
382-
)
383-
},
384-
385-
#' @description Set arguments to pass on to [jsonlite::toJSON()]
386-
#' @param x a named `list`
387-
set_toJSON_args = function(x){
388-
assert(is.list(x))
389-
assert(identical(length(names(x)), length(x)))
390-
private$.toJSON_args <- x
391-
invisible(self)
392-
},
393-
394-
395-
# . . setters -------------------------------------------------------------
396-
397-
#' @description Set a format that this Layout will apply to timestamps.
398-
#'
399-
#' @param x
400-
#' * `NULL` (the default): formatting of the timestamp is left to
401-
#' [jsonlite::toJSON()],
402-
#' * a `character` scalar as for [format.POSIXct()], or
403-
#' * a `function` that returns a vector of the same length as its
404-
#' ([POSIXct]) input. The returned vector can be of any type
405-
#' supported by [jsonlite::toJSON()], but should usually be `character`.
406-
set_timestamp_fmt = function(x){
407-
assert(is.null(x) || is_scalar_character(x) || is.function(x))
408-
private[[".timestamp_fmt"]] <- x
409-
invisible(self)
410-
},
411-
412-
# . . methods ----------------------------------------------------------------
413-
414-
toString = function() {
415-
fmt_class(class(self)[[1]])
416-
},
417-
418-
parse = function(
419-
file
420-
){
421-
read_json_lines(file)
422-
},
423-
424-
read = function(
425-
file,
426-
threshold = NA_integer_,
427-
n = 20L
428-
){
429-
assert(is_scalar_integerish(n))
430-
threshold <- standardize_threshold(threshold)
431-
432-
dd <- readLines(file)
433-
if (!is.na(threshold)){
434-
sel <- self$parse(file)$level <= threshold
435-
} else {
436-
sel <- TRUE
437-
}
438-
439-
dd <- tail(dd[sel], n)
440-
dd
441-
}
442-
443-
),
444-
445-
446-
# . . active fields ------------------------------------------------------
447-
448-
active = list(
449-
#' @field toJSON_args a list of values passed on to [jsonlite::toJSON()]
450-
toJSON_args = function() {
451-
get(".toJSON_args", private)
452-
},
453-
454-
#' @field timestamp_fmt Used by `$format_event()` to format timestamps.
455-
timestamp_fmt = function() {
456-
get(".timestamp_fmt", private)
457-
}
458-
),
459-
460-
# . . private --------------------------------------------------------------
461-
private = list(
462-
.toJSON_args = NULL,
463-
.timestamp_fmt = NULL
464-
)
465-
)
466-
467329

468330

469331

R/LayoutJson.R

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# LayoutJson --------------------------------------------------------------
2+
3+
#' Format LogEvents as JSON
4+
#'
5+
#' @description
6+
#' A format for formatting LogEvents as
7+
#' [jsonlines](https://jsonlines.org/) log files. This provides a
8+
#' nice balance between human- an machine-readability.
9+
#'
10+
#' This Layout provides 4 ways to transform the event before ingestion into
11+
#' Dynatrace:
12+
#'
13+
#' 1. `transform_event` a generic function to transform the event
14+
#' 2. `timestamp_fmt`
15+
#' 2. `transform_event_names` a named `character` vector or a second
16+
#' function to rename fields
17+
#' 3. `excluded_fields` a `character` vector to include fields.
18+
#'
19+
#' In theory supplying a custom `transform_event` function is enough to
20+
#' transform the event, but the other two parameters are provided for
21+
#' convenience. Please note that they are applied in order (e.g. if you
22+
#' rename a field you have to exclude the *renamed* field to really exclude it).
23+
#'
24+
#' @family Layouts
25+
#' @seealso [read_json_lines()], [https://jsonlines.org/](https://jsonlines.org/)
26+
#' @export
27+
#' @examples
28+
#' # setup a dummy LogEvent
29+
#' event <- LogEvent$new(
30+
#' logger = Logger$new("dummy logger"),
31+
#' level = 200,
32+
#' timestamp = Sys.time(),
33+
#' caller = NA_character_,
34+
#' msg = "a test message",
35+
#' custom_field = "LayoutJson can handle arbitrary fields"
36+
#' )
37+
#'
38+
#' # Default settings show all event fals
39+
#' lo <- LayoutJson$new()
40+
#' lo$format_event(event)
41+
LayoutJson <- R6::R6Class(
42+
"LayoutJson",
43+
inherit = Layout,
44+
public = list(
45+
46+
#' @description
47+
#' Creates a new instance of this [R6][R6::R6Class] class.
48+
#'
49+
#' @param toJSON_args a list of arguments passed to [jsonlite::toJSON()],
50+
#'
51+
#' @param timestamp_fmpt
52+
#' * `NULL` (the default): formatting of the timestamp is left to
53+
#' [jsonlite::toJSON()],
54+
#' * a `character` scalar as for [format.POSIXct()], or
55+
#' * a `function` that returns a vector of the same length as its
56+
#' ([POSIXct]) input. The returned vector can be of any type
57+
#' supported by [jsonlite::toJSON()], but should usually be `character`.
58+
#'
59+
#' @param transform_event a `function` with a single argument `event` that
60+
#' takes a [LogEvent] object and returns a list of values.
61+
#'
62+
#' @param excluded_fields A `character` vector of field names to exclude
63+
#' from the final output. passed to `transform_event()`.
64+
#'
65+
#' @param transform_event_names A named `character` vector mapping original
66+
#' field names to Dynatrace-compatible ones, or a function with a single
67+
#' mandatory argument that accepts a character vector of field names.
68+
#' passed to [transform_event()].
69+
initialize = function(
70+
toJSON_args = list(auto_unbox = TRUE),
71+
timestamp_fmt = NULL,
72+
transform_event = function(event) event[["values"]],
73+
transform_event_names = identity,
74+
excluded_fields = "rawMsg"
75+
){
76+
self$set_toJSON_args(toJSON_args)
77+
self$set_timestamp_fmt(timestamp_fmt)
78+
self$set_transform_event(transform_event)
79+
self$set_transform_event_names(transform_event_names)
80+
self$set_excluded_fields(excluded_fields)
81+
},
82+
83+
format_event = function(event) {
84+
85+
values <- get(".transform_event", private)(event)
86+
87+
if (!is.null(self[["timestamp_fmt"]])){
88+
values[["timestamp"]] <- fmt_timestamp(values[["timestamp"]], self[["timestamp_fmt"]])
89+
}
90+
91+
if (is.character(self$transform_event_names)){
92+
original_names <- names(values)
93+
rename_idx <- match(original_names, names(self$transform_event_names), nomatch = 0L)
94+
names(values)[rename_idx > 0L] <- self$transform_event_names[rename_idx[rename_idx > 0L]]
95+
96+
} else if (is.function(self$transform_event_names)){
97+
names(values) <- self$transform_event_names(names(values))
98+
99+
} else {
100+
warning("`transform_event_names` must be a character vector or a function")
101+
}
102+
103+
if (!is.null(self$excluded_fields)) {
104+
values <- values[!names(values) %in% self$excluded_fields]
105+
}
106+
107+
do.call(
108+
jsonlite::toJSON,
109+
args = c(list(x = values), get(".toJSON_args", private))
110+
)
111+
},
112+
113+
set_toJSON_args = function(x){
114+
assert(is.list(x))
115+
assert(identical(length(names(x)), length(x)))
116+
private$.toJSON_args <- x
117+
invisible(self)
118+
},
119+
120+
121+
# . . setters -------------------------------------------------------------
122+
123+
set_timestamp_fmt = function(x){
124+
assert(is.null(x) || is_scalar_character(x) || is.function(x))
125+
private[[".timestamp_fmt"]] <- x
126+
invisible(self)
127+
},
128+
129+
set_transform_event = function(x){
130+
assert(
131+
is.function(x) &&
132+
identical(names(formals(x)), "event"),
133+
"`transform_event` must be a function a single argument `event`")
134+
135+
private[[".transform_event"]] <- x
136+
invisible(self)
137+
},
138+
139+
set_transform_event_names = function(x){
140+
assert(is.function(x) || is_field_name_map(x),
141+
"`transform_event_names` must be a named character vector or function with a single mandatory argument (optional arguments are OK)")
142+
143+
private[[".transform_event_names"]] <- x
144+
},
145+
146+
# . . methods ----------------------------------------------------------------
147+
148+
toString = function() {
149+
fmt_class(class(self)[[1]])
150+
},
151+
152+
parse = function(
153+
file
154+
){
155+
read_json_lines(file)
156+
},
157+
158+
read = function(
159+
file,
160+
threshold = NA_integer_,
161+
n = 20L
162+
){
163+
assert(is_scalar_integerish(n))
164+
threshold <- standardize_threshold(threshold)
165+
166+
dd <- readLines(file)
167+
if (!is.na(threshold)){
168+
sel <- self$parse(file)$level <= threshold
169+
} else {
170+
sel <- TRUE
171+
}
172+
173+
dd <- tail(dd[sel], n)
174+
dd
175+
}
176+
),
177+
178+
179+
# . . active fields ------------------------------------------------------
180+
181+
active = list(
182+
toJSON_args = function() {
183+
get(".toJSON_args", private)
184+
},
185+
186+
timestamp_fmt = function() {
187+
get(".timestamp_fmt", private)
188+
},
189+
190+
transform_event = function() get(".transform_event", private),
191+
192+
transform_event_names = function() get(".transform_event_names", private)
193+
),
194+
195+
# . . private --------------------------------------------------------------
196+
private = list(
197+
.toJSON_args = NULL,
198+
.timestamp_fmt = NULL,
199+
.transform_event = NULL,
200+
.transform_event_names = NULL
201+
)
202+
)
203+
204+
205+
is_field_name_map <- function(x){
206+
is.character(x) && !is.null(names(x)) && all(nzchar(names(x)))
207+
}

0 commit comments

Comments
 (0)