-
Notifications
You must be signed in to change notification settings - Fork 38
/
layouts.R
288 lines (241 loc) · 12.9 KB
/
layouts.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#' Collect useful information about the logging environment to be used in log messages
#'
#' Available variables to be used in the log formatter functions, eg in \code{\link{layout_glue_generator}}:
#' \itemize{
#' \item levelr: log level as an R object, eg \code{\link{INFO}}
#' \item level: log level as a string, eg \code{\link{INFO}}
#' \item time: current time as \code{POSIXct}
#' \item node: name by which the machine is known on the network as reported by \code{Sys.info}
#' \item arch: machine type, typically the CPU architecture
#' \item os_name: Operating System's name
#' \item os_release: Operating System's release
#' \item os_version: Operating System's version
#' \item user: name of the real user id as reported by \code{Sys.info}
#' \item pid: the process identification number of the R session
#' \item node: name by which the machine is known on the network as reported by \code{Sys.info}
#' \item r_version: R's major and minor version as a string
#' \item ns: namespace usually defaults to \code{global} or the name of the holding R package of the calling the logging function
#' \item ns_pkg_version: the version of \code{ns} when it's a package
#' \item ans: same as \code{ns} if there's a defined \code{\link{logger}} for the namespace, otherwise a fallback namespace (eg usually \code{global})
#' \item topenv: the name of the top environment from which the parent call was called (eg R package name or \code{GlobalEnv})
#' \item call: parent call (if any) calling the logging function
#' \item fn: function's (if any) name calling the logging function
#' }
#' @param log_level log level as per \code{\link{log_levels}}
#' @inheritParams log_level
#' @return list
#' @export
#' @importFrom utils packageVersion
#' @seealso \code{\link{layout_glue_generator}}
get_logger_meta_variables <- function(log_level = NULL, namespace = NA_character_,
.logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) {
sysinfo <- Sys.info()
timestamp <- Sys.time()
list(
ns = namespace,
ans = fallback_namespace(namespace),
topenv = top_env_name(.topenv),
fn = deparse_to_one_line(.topcall[[1]]),
call = deparse_to_one_line(.topcall),
time = timestamp,
levelr = log_level,
level = attr(log_level, 'level'),
pid = Sys.getpid(),
## R and ns package versions
r_version = paste0(R.Version()[c('major', 'minor')], collapse = '.'),
ns_pkg_version = tryCatch(as.character(packageVersion(namespace)), error = function(e) NA_character_),
## stuff from Sys.info
node = sysinfo[['nodename']],
arch = sysinfo[['machine']],
os_name = sysinfo[['sysname']],
os_release = sysinfo[['release']],
os_version = sysinfo[['version']],
user = sysinfo[['user']]
## NOTE might be better to rely on the whoami pkg?
## TODO jenkins (or any) env vars => no need to get here, users can write custom layouts
## TODO seed
)
}
#' Generate log layout function using common variables available via glue syntax
#'
#' \code{format} is passed to \code{glue} with access to the below variables:
#' \itemize{
#' \item msg: the actual log message
#' \item further variables set by \code{\link{get_logger_meta_variables}}
#' }
#' @param format \code{glue}-flavored layout of the message using the above variables
#' @return function taking \code{level} and \code{msg} arguments - keeping the original call creating the generator in the \code{generator} attribute that is returned when calling \code{\link{log_layout}} for the currently used layout
#' @export
#' @examples \dontrun{
#' example_layout <- layout_glue_generator(
#' format = '{node}/{pid}/{ns}/{ans}/{topenv}/{fn} {time} {level}: {msg}')
#' example_layout(INFO, 'try {runif(1)}')
#'
#' log_layout(example_layout)
#' log_info('try {runif(1)}')
#' }
#' @seealso See example calls from \code{\link{layout_glue}} and \code{\link{layout_glue_colors}}.
layout_glue_generator <- function(format = '{level} [{format(time, "%Y-%m-%d %H:%M:%S")}] {msg}') {
force(format)
structure(function(level, msg, namespace = NA_character_,
.logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) {
fail_on_missing_package('glue')
if (!inherits(level, 'loglevel')) {
stop('Invalid log level, see ?log_levels')
}
with(get_logger_meta_variables(
log_level = level, namespace = namespace,
.logcall = .logcall, .topcall = .topcall, .topenv = .topenv),
glue::glue(format))
}, generator = deparse(match.call()))
}
#' Format a log record by including the raw message without anything added or modified
#' @inheritParams log_level
#' @param msg string message
#' @return character vector
#' @export
#' @seealso This is a \code{\link{log_layout}}, for alternatives, see \code{\link{layout_simple}}, \code{\link{layout_glue_colors}}, \code{\link{layout_json}}, or generator functions such as \code{\link{layout_glue_generator}}
layout_blank <- structure(function(level, msg, namespace = NA_character_,
.logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) {
msg
}, generator = quote(layout_blank()))
#' Format a log record by concatenating the log level, timestamp and message
#' @inheritParams log_level
#' @param msg string message
#' @return character vector
#' @export
#' @seealso This is a \code{\link{log_layout}}, for alternatives, see \code{\link{layout_blank}}, \code{\link{layout_glue}}, \code{\link{layout_glue_colors}}, \code{\link{layout_json}}, \code{\link{layout_json_parser}}, or generator functions such as \code{\link{layout_glue_generator}}
layout_simple <- structure(function(level, msg, namespace = NA_character_,
.logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) {
paste0(attr(level, 'level'), ' [', format(Sys.time(), "%Y-%m-%d %H:%M:%S"), '] ', msg)
}, generator = quote(layout_simple()))
#' Format a log record as the logging package does by default
#' @inheritParams layout_simple
#' @param msg string message
#' @return character vector
#' @export
#' @seealso This is a \code{\link{log_layout}}, for alternatives, see \code{\link{layout_blank}}, \code{\link{layout_glue}}, \code{\link{layout_glue_colors}}, \code{\link{layout_json}}, \code{\link{layout_json_parser}}, or generator functions such as \code{\link{layout_glue_generator}}
#' @examples \dontrun{
#' log_layout(layout_logging)
#' log_info(42)
#' log_info(42, namespace = 'everything')
#'
#' devtools::load_all(system.file('demo-packages/logger-tester-package', package = 'logger'))
#' logger_tester_function(INFO, 42)
#' }
layout_logging <- structure(function(level, msg, namespace = NA_character_,
.logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) {
meta <- get_logger_meta_variables(
log_level = level, namespace = namespace,
.logcall = .logcall, .topcall = .topcall, .topenv = .topenv)
paste0(format(Sys.time(), "%Y-%m-%d %H:%M:%S"), ' ',
attr(level, 'level'), ':',
ifelse(meta$ns == 'global', '', meta$ns), ':',
msg)
}, generator = quote(layout_logging()))
#' Format a log message with \code{glue}
#'
#' By default, this layout includes the log level of the log record as per \code{\link{log_levels}}, the current timestamp and the actual log message -- that you can override via calling \code{\link{layout_glue_generator}} directly. For colorized output, see \code{\link{layout_glue_colors}}.
#' @inheritParams layout_simple
#' @return character vector
#' @export
#' @seealso This is a \code{\link{log_layout}}, for alternatives, see \code{\link{layout_blank}}, \code{\link{layout_simple}}, \code{\link{layout_glue_colors}}, \code{\link{layout_json}}, \code{\link{layout_json_parser}}, or generator functions such as \code{\link{layout_glue_generator}}
layout_glue <- layout_glue_generator()
#' Format a log message with \code{glue} and ANSI escape codes to add colors
#' @inheritParams layout_simple
#' @return character vector
#' @export
#' @examples \dontrun{
#' log_layout(layout_glue_colors)
#' log_threshold(TRACE)
#' log_info('Starting the script...')
#' log_debug('This is the second line')
#' log_trace('That is being placed right after the first one.')
#' log_warn('Some errors might come!')
#' log_error('This is a problem')
#' log_debug('Getting an error is usually bad')
#' log_error('This is another problem')
#' log_fatal('The last problem.')
#' }
#' @seealso This is a \code{\link{log_layout}}, for alternatives, see \code{\link{layout_blank}}, \code{\link{layout_simple}}, \code{\link{layout_glue}}, \code{\link{layout_json}}, \code{\link{layout_json_parser}}, or generator functions such as \code{\link{layout_glue_generator}}
#' @note This functionality depends on the \pkg{crayon} package.
layout_glue_colors <- layout_glue_generator(
format = paste(
'{crayon::bold(colorize_by_log_level(level, levelr))}',
'[{crayon::italic(format(time, "%Y-%m-%d %H:%M:%S"))}]',
'{grayscale_by_log_level(msg, levelr)}'))
#' Generate log layout function rendering JSON
#' @param fields character vector of field names to be included in the JSON
#' @return character vector
#' @export
#' @examples \dontrun{
#' log_layout(layout_json())
#' log_info(42)
#' log_info('ok {1:3} + {1:3} = {2*(1:3)}')
#' }
#' @note This functionality depends on the \pkg{jsonlite} package.
#' @seealso This is a \code{\link{log_layout}}, for alternatives, see \code{\link{layout_blank}}, \code{\link{layout_simple}}, \code{\link{layout_glue}}, \code{\link{layout_glue_colors}}, \code{\link{layout_json_parser}}, or generator functions such as \code{\link{layout_glue_generator}}
layout_json <- function(fields = c('time', 'level', 'ns', 'ans', 'topenv', 'fn', 'node', 'arch', 'os_name', 'os_release', 'os_version', 'pid', 'user', 'msg')) {
force(fields)
structure(function(level, msg, namespace = NA_character_,
.logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) {
fail_on_missing_package('jsonlite')
json <- get_logger_meta_variables(
log_level = level, namespace = namespace,
.logcall = .logcall, .topcall = .topcall, .topenv = .topenv)
sapply(msg, function(msg) jsonlite::toJSON(c(json, list(msg = msg))[fields], auto_unbox = TRUE))
}, generator = deparse(match.call()))
}
#' Generate log layout function rendering JSON after merging meta fields with parsed list from JSON message
#' @param fields character vector of field names to be included in the JSON
#' @export
#' @examples \dontrun{
#' log_formatter(formatter_json)
#' log_info(everything = 42)
#' log_layout(layout_json_parser())
#' log_info(everything = 42)
#' log_layout(layout_json_parser(fields = c('time', 'node')))
#' log_info(cars = row.names(mtcars), species = unique(iris$Species))
#' }
#' @note This functionality depends on the \pkg{jsonlite} package.
#' @seealso This is a \code{\link{log_layout}} potentially to be used with \code{\link{formatter_json}}, for alternatives, see \code{\link{layout_simple}}, \code{\link{layout_glue}}, \code{\link{layout_glue_colors}}, \code{\link{layout_json}} or generator functions such as \code{\link{layout_glue_generator}}
layout_json_parser <- function(fields = c('time', 'level', 'ns', 'ans', 'topenv', 'fn', 'node', 'arch', 'os_name', 'os_release', 'os_version', 'pid', 'user')) {
force(fields)
structure(function(level, msg, namespace = NA_character_,
.logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) {
fail_on_missing_package('jsonlite')
meta <- get_logger_meta_variables(
log_level = level, namespace = namespace,
.logcall = .logcall, .topcall = .topcall, .topenv = .topenv)[fields]
msg <- jsonlite::fromJSON(msg)
jsonlite::toJSON(c(meta, msg), auto_unbox = TRUE, null = 'null')
}, generator = deparse(match.call()))
}
#nocov start
#' Format a log record for syslognet
#'
#' Format a log record for syslognet.
#' This function converts the logger log level to a
#' log severity level according to RFC 5424 "The Syslog Protocol".
#'
#' @inheritParams layout_simple
#' @return A character vector with a severity attribute.
#' @export
layout_syslognet <- structure(
function(level, msg, namespace = NA_character_,
.logcall = sys.call(), .topcall = sys.call(-1), .topenv = parent.frame()) {
ret <- paste(attr(level, 'level'), msg)
attr(ret, 'severity') <- switch(
attr(level, 'level', exact = TRUE),
'FATAL' = 'CRITICAL',
'ERROR' = 'ERR',
'WARN' = 'WARNING',
'SUCCESS' = 'NOTICE',
'INFO' = 'INFO',
'DEBUG' = 'DEBUG',
'TRACE' = 'DEBUG')
return(ret)
},
generator = quote(layout_syslognet())
)
#nocov end