The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
The stenographer
package provides a flexible and
powerful logging system for R applications. It includes a
Stenographer
class for creating customisable loggers, as
well as helper functions for debugging and error reporting. This
vignette will guide you through the basics of using the
stenographer
package and demonstrate how to leverage its
features to improve your R workflows.
You can install the released version of stenographer from CRAN:
install.packages("stenographer")
You can install stenographer from www.github.com/dereckmezquita/stenographer with:
::install_github("dereckmezquita/stenographer") remotes
First, let’s load the package and create a basic stenographer:
::use(stenographer[Stenographer, LogLevel, messageParallel])
box
# Create a basic logger
<- Stenographer$new()
steno
# Log some messages
$info("This is an informational message")
steno#> 2025-01-15T08:37:58.991Z INFO This is an informational message
$warn("This is a warning")
steno#> 2025-01-15T08:37:58.993Z WARNING This is a warning
$error("This is an error")
steno#> 2025-01-15T08:37:59.003Z ERROR This is an error
You can customise the Stenographer
by specifying the
minimum log level, output file, and custom print function:
# Create a custom stenographer
<- Stenographer$new(
custom_steno level = LogLevel$WARNING,
file_path = "app.log",
print_fn = message
)
$info("This won't be logged")
custom_steno$warn("This will be logged to console and file")
custom_steno#> 2025-01-15T08:37:59.114Z WARNING This will be logged to console and file
$error("This is an error message")
custom_steno#> 2025-01-15T08:37:59.115Z ERROR This is an error message
The Stenographer
class supports logging to a
SQLite
database. Here’s how you can set it up:
::use(RSQLite[ SQLite ])
box::use(DBI[ dbConnect, dbDisconnect, dbGetQuery ])
box
# Create a database connection
<- dbConnect(SQLite(), "log.sqlite")
db
# Create a Stenographer that logs to the database
<- Stenographer$new(
db_steno db_conn = db,
table_name = "app_logs"
)
# Log some messages
$info("This is logged to the database")
db_steno#> 2025-01-15T08:37:59.222Z INFO This is logged to the database
$warn("This is a warning", data = list(code = 101))
db_steno#> 2025-01-15T08:37:59.227Z WARNING This is a warning
#> Data:
#> {
#> "code": 101
#> }
$error("An error occurred", error = "Division by zero")
db_steno#> 2025-01-15T08:37:59.248Z ERROR An error occurred
#> Error:
#> "Division by zero"
# Example of querying the logs
<- "SELECT * FROM app_logs WHERE level = 'ERROR'"
query <- dbGetQuery(db, query)
result print(result)
#> id datetime level context msg data
#> 1 3 2025-01-15T08:37:59.248Z ERROR <NA> An error occurred <NA>
#> error
#> 1 ["[\\"Division by zero\\"]"]
The Stenographer
class supports a context feature, which
allows you to add persistent information to your log entries:
<- Stenographer$new(
context_steno db_conn = db,
table_name = "context_logs",
context = list(app_name = "MyApp", version = "1.0.0")
)
$info("Application started")
context_steno#> 2025-01-15T08:37:59.284Z INFO Application started
#> Context:
#> {
#> "app_name": "MyApp",
#> "version": "1.0.0"
#> }
# Update context
$update_context(list(user_id = "12345"))
context_steno$info("User logged in")
context_steno#> 2025-01-15T08:37:59.289Z INFO User logged in
#> Context:
#> {
#> "app_name": "MyApp",
#> "version": "1.0.0",
#> "user_id": "12345"
#> }
# Log an error with context
$error("Operation failed", data = list(operation = "data_fetch"))
context_steno#> 2025-01-15T08:37:59.293Z ERROR Operation failed
#> Data:
#> {
#> "operation": "data_fetch"
#> }
#> Context:
#> {
#> "app_name": "MyApp",
#> "version": "1.0.0",
#> "user_id": "12345"
#> }
# Example of querying logs with context
<- "SELECT * FROM context_logs WHERE json_extract(context, '$.user_id') = '12345'"
query <- dbGetQuery(db, query)
result print(result)
#> [1] id datetime level context msg data error
#> <0 rows> (or 0-length row.names)
# Clear context
$clear_context()
context_steno$info("Context cleared")
context_steno#> 2025-01-15T08:37:59.301Z INFO Context cleared
You can combine various features of the Stenographer
class to create a powerful logging system:
# Create a combined Stenographer
<- Stenographer$new(
combined_steno level = LogLevel$INFO,
file_path = "combined_app.log",
db_conn = db,
table_name = "combined_logs",
context = list(app_name = "CombinedApp", version = "2.0.0"),
print_fn = messageParallel,
format_fn = function(level, msg) {
# manipulate the message before logging
<- gsub("API_KEY=[^\\s]+", "API_KEY=***", msg)
msg return(paste(level, msg))
}
)
# Log some messages
$info("Application started")
combined_steno$warn("Low memory", data = list(available_mb = 100))
combined_steno$error("Database connection failed", error = "Connection timeout")
combined_steno
# Update context
$update_context(list(user_id = "67890"))
combined_steno$info("User action", data = list(action = "button_click"))
combined_steno
# Example of a more complex query using context and data
<- "
query SELECT *
FROM combined_logs
WHERE json_extract(context, '$.app_name') = 'CombinedApp'
AND json_extract(data, '$.available_mb') < 200
"
<- dbGetQuery(db, query)
result print(result)
#> [1] id datetime level context msg data error
#> <0 rows> (or 0-length row.names)
# Don't forget to close the database connection when you're done
dbDisconnect(db)
The Stenographer
package includes several helper
functions that can be used in conjunction with the
Stenographer
class to provide more detailed information in
your logs. Let’s explore how to use these functions effectively.
Suppose we have a dataset with some problematic values, and we want
to log where these issues occur. We can use the
valueCoordinates
function to locate the problematic values
and include this information in our log messages.
::use(stenographer[valueCoordinates])
box
# Create a sample dataset with some issues
<- data.frame(
df a = c(1, NA, 3, 4, 5),
b = c(2, 4, NA, 8, 10),
c = c(3, 6, 9, NA, 15)
)
# Create a Stenographer
<- Stenographer$new()
steno
# Find coordinates of NA values
<- valueCoordinates(df)
na_coords
if (nrow(na_coords) > 0) {
$warn(
steno"NA values found in the dataset",
data = list(
na_locations = na_coords
)
)
}#> 2025-01-15T08:37:59.393Z WARNING NA values found in the dataset
#> Data:
#> {
#> "na_locations": [
#> {
#> "column": 1,
#> "row": 2
#> },
#> {
#> "column": 2,
#> "row": 3
#> },
#> {
#> "column": 3,
#> "row": 4
#> }
#> ]
#> }
This will produce a log entry like:
When an error occurs, it’s often useful to catch and log not just the
error message, but also the context in which the error occurred. Here’s
an example of how to do this using the Stenographer
class
and helper functions:
::use(stenographer[tableToString])
box
<- Stenographer$new()
steno
<- function(df) {
process_data tryCatch({
<- df$a / df$b
result if (any(is.infinite(result))) {
<- valueCoordinates(data.frame(result), Inf)
inf_coords $error(
steno"Division by zero occurred",
data = list(
infinite_values = inf_coords,
dataset_preview = tableToString(df)
)
)cat("Division by zero error")
}return(result)
error = function(e) {
}, $error(
stenopaste("An error occurred while processing data:", e$message),
data = list(dataset_preview = tableToString(df)),
error = e
)cat(e)
})
}
# Test the function with problematic data
<- data.frame(a = c(1, 2, 3), b = c(0, 2, 0))
df process_data(df)
#> 2025-01-15T08:37:59.439Z ERROR Division by zero occurred
#> Data:
#> {
#> "infinite_values": [
#> {
#> "column": 1,
#> "row": 1
#> },
#> {
#> "column": 1,
#> "row": 3
#> }
#> ],
#> "dataset_preview": " a b\n1 1 0\n2 2 2\n3 3 0"
#> }
#> Division by zero error
#> [1] Inf 1 Inf
When working with parallel processing, standard logging functions
might not work as expected. The stenographer package provides a
messageParallel
function to ensure messages are properly
logged from parallel processes:
::use(future)
box::use(future.apply[future_lapply])
box
<- Stenographer$new(print_fn = messageParallel)
steno
::plan(future::multisession, workers = 2)
future
<- future_lapply(1:5, function(i) {
result messageParallel(sprintf("Processing item %d", i))
if (i == 3) {
$warn(sprintf("Warning for item %d", i))
steno
}return(i * 2)
})
::plan(future::sequential) future
This ensures that messages from parallel processes are properly captured and logged.
The stenographer package provides a robust and flexible logging system for R applications. With features like file logging, database logging, and context management, you can create informative and context-rich log messages that greatly aid in debugging and monitoring your R scripts and applications.
Moreover, by using helper functions like
valueCoordinates
and tableToString
you can
more easily track down and log data issues and errors, providing
valuable information for troubleshooting and analysis.
Remember to adjust the log level, output file, database settings, and other parameters to suit your specific needs. The ability to query logs using SQL, especially with context-based filtering, makes it easy to analyze and troubleshoot issues in your applications.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.