Im trying to deploy a R Plumber test API as a docker container as my main goal is to successfully create a log file everytime the API processes a request. I have tested it locally and it works.
Here are my test files:
plumber.R
# plumber.R
# A simple API to illustrate logging with Plumber
library(plumber)
#* @apiTitle Logging Example
#* @apiDescription Simple example API for implementing logging with Plumber
#* Echo back the input
#* @param msg The message to echo
#* @get /echo
function(msg = "") {
list(msg = paste0("The message is: '", msg, "'"))
}
entrypoint.R
library(plumber)
# Config
#config <- config::get()
# logging
library(logger)
# Ensure glue is a specific dependency so it's avaible for logger
library(glue)
# Specify how logs are written
log_dir <- "/app/logs"
if (!fs::dir_exists(log_dir)) fs::dir_create(log_dir)
log_appender(appender_tee(tempfile("plumber_", log_dir, ".log")))
convert_empty <- function(string) {
if (string == "") {
"-"
} else {
string
}
}
pr <- plumb("plumber.R")
pr$registerHooks(
list(
preroute = function() {
# Start timer for log info
tictoc::tic()
},
postroute = function(req, res) {
end <- tictoc::toc(quiet = TRUE)
# Log details about the request and the response
# TODO: Sanitize log details - perhaps in convert_empty
log_info('{convert_empty(req$REMOTE_ADDR)} "{convert_empty(req$HTTP_USER_AGENT)}" {convert_empty(req$HTTP_HOST)} {convert_empty(req$REQUEST_METHOD)} {convert_empty(req$PATH_INFO)} {convert_empty(res$status)} {round(end$toc - end$tic, digits = getOption("digits", 5))}')
}
)
)
pr
Dockerfile
# Base image https://hub.docker.com/u/rocker/
FROM rstudio/plumber
# Install R libraries
RUN R -e "install.packages(c('odbc','glue','parsedate','tibble','dplyr','httr','RCurl','jsonlite','rjson','stringr','telegram','R.utils','logger','tictoc'))"
# Run plumber package
CMD ["/app/plumber.R"]
My expected result is to be able to store the log in a shared folder between the container and the host. But so far I havent been able to have the plumber app even to even create a log file with logs inside /app/logs.
docker build -t logger2 .
docker run -d -p 18001:8000 --rm \
-v /home/user/R/projects/logger2/:/app \
-v /tmp/log1:/app/logs \
logger2
The problem here is the entrypoint. Your entrypoint.R script is being ignored.
Here's the default entrypoint that the rplumber image comes with:
ENTRYPOINT ["R", "-e", "pr <- plumber::plumb(rev(commandArgs())[1]); args <- list(host = '0.0.0.0', port = 8000); if (packageVersion('plumber') >= '1.0.0') { pr$setDocs(TRUE) } else { args$swagger <- TRUE }; do.call(pr$run, args)"]
Here's what that does:
In this case, it loads the /app/plumber.R
file, and ignores the /app/entrypoint.R
file.
How can you fix that? Define your own entrypoint in the Dockerfile.
FROM rstudio/plumber
# Install R libraries
RUN R -e "install.packages(c('odbc','glue','parsedate','tibble','dplyr','httr','RCurl','jsonlite','rjson','stringr','telegram','R.utils','logger','tictoc'))"
ENTRYPOINT ["Rscript", "/app/entrypoint.R"]
Here I still ran into a few problems:
RUN R -e "install.packages(c('fs'))"
to install it.Error in plumb("plumber.R") : File does not exist: plumber.R
. This is because the server is running in the wrong directory. I fixed this by adding WORKDIR /app
to your Dockerfile.entrypoint.R
from pr
to pr$run(host='0.0.0.0', port=8000)
to make it actually run.Full Dockerfile:
FROM rstudio/plumber
# Install R libraries
RUN R -e "install.packages(c('odbc','glue','parsedate','tibble','dplyr','httr','RCurl','jsonlite','rjson','stringr','telegram','R.utils','logger','tictoc'))"
RUN R -e "install.packages(c('fs'))"
WORKDIR /app
ENTRYPOINT ["Rscript", "/app/entrypoint.R"]
Full entrypoint.R
file:
library(plumber)
# Config
#config <- config::get()
# logging
library(logger)
# Ensure glue is a specific dependency so it's avaible for logger
library(glue)
# Specify how logs are written
log_dir <- "/app/logs"
if (!fs::dir_exists(log_dir)) fs::dir_create(log_dir)
log_appender(appender_tee(tempfile("plumber_", log_dir, ".log")))
convert_empty <- function(string) {
if (string == "") {
"-"
} else {
string
}
}
pr <- plumb("plumber.R")
pr$registerHooks(
list(
preroute = function() {
# Start timer for log info
tictoc::tic()
},
postroute = function(req, res) {
end <- tictoc::toc(quiet = TRUE)
# Log details about the request and the response
# TODO: Sanitize log details - perhaps in convert_empty
log_info('{convert_empty(req$REMOTE_ADDR)} "{convert_empty(req$HTTP_USER_AGENT)}" {convert_empty(req$HTTP_HOST)} {convert_empty(req$REQUEST_METHOD)} {convert_empty(req$PATH_INFO)} {convert_empty(res$status)} {round(end$toc - end$tic, digits = getOption("digits", 5))}')
}
)
)
pr$run(host='0.0.0.0', port=8000)