|
| 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