<!--
%\VignetteIndexEntry{Introduction to RcppSpdlog}
%\VignetteEngine{simplermarkdown::mdweave_to_html}
%\VignetteEncoding{UTF-8}
-->
---
title: "Introduction to RcppSpdlog"
author: "Dirk Eddelbuettel"
date: "Initial version dated October 2020. Expanded November 2022."
css: "water.css"
---
## Introducing RcppSpdlog
[spdlog](https://github.com/gabime/spdlog) is a widely-used and very capable header-only C++ library for logging.
The [RcppSpdlog](https://github.com/eddelbuettel/rcppspdlog) package provides R users with easy-to-use customized access to the [spdlog](https://github.com/gabime/spdlog) logging library by including its headers in an R package which permit other R packages to deploy it via a simple `LinkingTo: RcppSpdlog` as described in [Section 1.1.3 of WRE](https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Package-Dependencies).
[spdlog](https://github.com/gabime/spdlog) is mature and widely deployed.
It also has a very rich set of features described at [the repository wiki](https://github.com/gabime/spdlog/wiki).
This vignette will highlight a few first use cases.
Note that in order to use [RcppSpdlog](https://github.com/eddelbuettel/rcppspdlog) in an R package that might get distributed to [CRAN](https://cran.r-project.org), the code should follow the example
R and C++ code in function `exampleRsink()` as described below.
We will however start with some simpler examples.
_Do not copy those into your R package._
The package checks used by R test for use of `stdout` and `stderr` which is why the customized setup described later is preferable.
## Initial example: Basics
This example follows the simplest and initial example in the [spdlog](https://github.com/gabime/spdlog).
It is also included in the [RcppSpdlog](https://github.com/eddelbuettel/rcppspdlog) package as [examples/exampleOne.cpp](https://github.com/eddelbuettel/rcppspdlog/blob/master/inst/examples/exampleOne.cpp).
As discussed above, do not use this example as a starting point in an R package.
```c++
// based on the 'basic usage' example in the README.md at https://github.com/gabime/spdlog
#include "spdlog/spdlog.h"
#include <Rcpp.h>
// [[Rcpp::depends(RcppSpdlog)]]
// [[Rcpp::export]]
void exampleOne() {
// change log pattern (changed from [%H:%M:%S %z] [%n] [%^---%L---%$] )
spdlog::set_pattern("[%H:%M:%S.%f] [%L] [thread %t] %v");
spdlog::info("Welcome to spdlog!");
spdlog::error("Some error message with arg: {}", 1);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:<30}", "left aligned");
spdlog::set_level(spdlog::level::debug); // Set global log level to debug
spdlog::debug("This message should be displayed..");
// Compile time log levels
// define SPDLOG_ACTIVE_LEVEL to desired level
SPDLOG_TRACE("Some trace message with param {}", {});
SPDLOG_DEBUG("Some debug message");
}
/*** R
exampleOne()
*/
```
When built, which is easiest via `Rcpp::sourceCpp()`, the final block ensures that the created function `exampleOne()` is executed.
In one previous run, the following output was produded:
```sh
R> exampleOne()
[14:25:03.362024] [I] [thread 2453030] Welcome to spdlog!
[14:25:03.362047] [E] [thread 2453030] Some error message with arg: 1
[14:25:03.362051] [W] [thread 2453030] Easy padding in numbers like 00000012
[14:25:03.362053] [C] [thread 2453030] Support for int: 42; hex: 2a; oct: 52; bin: 101010
[14:25:03.362056] [I] [thread 2453030] Support for floats 1.23
[14:25:03.362058] [I] [thread 2453030] Positional args are supported too..
[14:25:03.362060] [I] [thread 2453030] left aligned
[14:25:03.362061] [D] [thread 2453030] This message should be displayed..
R>
```
We note the easy-to-formatting in the source which benefits from the embedded [fmt](https://github.com/fmtlib/fmt) package for easy-to-use variable expansion.
We also notice the different logging "levels" indicated by single letters: _info_, _errror_, _warning_, _critical_ and _debug_. More on this below.
## Second example: Showcase
This second example follows a more complete example in the [spdlog](https://github.com/gabime/spdlog) documention and highlights numerous features of the library.
As before, this example is also included in the [RcppSpdlog](https://github.com/eddelbuettel/rcppspdlog) package as [examples/exampleTwo.cpp](https://github.com/eddelbuettel/rcppspdlog/blob/master/inst/examples/exampleTwo.cpp).
And as before, do not use this example as a starting point in an R package.
```c++
#include "spdlog/spdlog.h"
#include <Rcpp.h>
void stdout_logger_example();
void basic_example();
void rotating_example();
void daily_example();
void async_example();
void binary_example();
void trace_example();
void multi_sink_example();
void user_defined_example();
void err_handler_example();
void syslog_example();
void clone_example();
#include "spdlog/spdlog.h"
// [[Rcpp::depends(RcppSpdlog)]]
// [[Rcpp::export]]
void exampleTwo() {
spdlog::info("Welcome to spdlog version {}.{}.{} !",
SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left");
// Runtime log levels
spdlog::set_level(spdlog::level::info); // Set global log level to info
spdlog::debug("This message should not be displayed!");
spdlog::set_level(spdlog::level::trace); // Set specific logger's log level
spdlog::debug("This message should be displayed..");
// Customize msg format for all loggers
spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v");
spdlog::info("This an info message with custom format");
spdlog::set_pattern("%+"); // back to default format
try
{
stdout_logger_example();
basic_example();
rotating_example();
daily_example();
clone_example();
async_example();
binary_example();
multi_sink_example();
user_defined_example();
err_handler_example();
trace_example();
// Flush all *registered* loggers using a worker thread every 3 seconds.
// note: registered loggers *must* be thread safe for this to work correctly!
spdlog::flush_every(std::chrono::seconds(3));
// Apply some function on all registered loggers
spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) { l->info("End of example."); });
// Release all spdlog resources, and drop all loggers in the registry.
// This is optional (only mandatory if using windows + async log).
//spdlog::shutdown();
}
// Exceptions will only be thrown upon failed logger or sink construction (not during logging).
catch (const spdlog::spdlog_ex &ex)
{
std::printf("Log initialization failed: %s\n", ex.what());
return;
}
// added to this example file allow multiple runs of function
spdlog::drop("console");
spdlog::drop("file_logger");
spdlog::drop("some_logger_name");
spdlog::drop("daily_logger");
spdlog::drop("async_file_logger");
}
#include "spdlog/sinks/stdout_color_sinks.h"
// or #include "spdlog/sinks/stdout_sinks.h" if no colors needed.
void stdout_logger_example()
{
// Create color multi threaded logger.
auto console = spdlog::stdout_color_mt("console");
// or for stderr:
// auto console = spdlog::stderr_color_mt("error-logger");
}
#include "spdlog/sinks/basic_file_sink.h"
void basic_example()
{
// Create basic file logger (not rotated).
auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt");
}
#include "spdlog/sinks/rotating_file_sink.h"
void rotating_example()
{
// Create a file rotating logger with 5mb size max and 3 rotated files.
auto rotating_logger =
spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3);
}
#include "spdlog/sinks/daily_file_sink.h"
void daily_example()
{
// Create a daily logger - a new file is created every day on 2:30am.
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
}
// Clone a logger and give it new name.
// Useful for creating component/subsystem loggers from some "root" logger.
void clone_example()
{
auto network_logger = spdlog::default_logger()->clone("network");
network_logger->info("Logging network stuff..");
}
#include "spdlog/async.h"
void async_example()
{
// Default thread pool settings can be modified *before* creating the async logger:
// spdlog::init_thread_pool(32768, 1); // queue with max 32k items 1 backing thread.
auto async_file =
spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt");
// alternatively:
// auto async_file =
// spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_file_logger",
// "logs/async_log.txt");
for (int i = 1; i < 101; ++i)
{
async_file->info("Async message #{}", i);
}
}
// Log binary data as hex.
// Many types of std::container<char> types can be used.
// Iterator ranges are supported too.
// Format flags:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output to lines.
#include "spdlog/fmt/bin_to_hex.h"
void binary_example()
{
std::vector<char> buf;
for (int i = 0; i < 80; i++)
{
buf.push_back(static_cast<char>(i & 0xff));
}
spdlog::info("Binary example: {}", spdlog::to_hex(buf));
spdlog::info("Another binary example:{:n}",
spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
// more examples:
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
}
// Compile time log levels.
// define SPDLOG_ACTIVE_LEVEL to required level (e.g. SPDLOG_LEVEL_TRACE)
void trace_example()
{
// trace from default logger
SPDLOG_TRACE("Some trace message.. {} ,{}", 1, 3.23);
// debug from default logger
SPDLOG_DEBUG("Some debug message.. {} ,{}", 1, 3.23);
// trace from logger object
auto logger = spdlog::get("file_logger");
SPDLOG_LOGGER_TRACE(logger, "another trace message");
}
// A logger with multiple sinks (stdout and file) - each with a different format and log level.
void multi_sink_example()
{
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::warn);
console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");
auto file_sink =
std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);
file_sink->set_level(spdlog::level::trace);
spdlog::logger logger("multi_sink", {console_sink, file_sink});
logger.set_level(spdlog::level::debug);
logger.warn("this should appear in both console and file");
logger.info("this message should not appear in the console, only in the file");
}
// User defined types logging by implementing operator<<
#include "spdlog/fmt/ostr.h" // must be included
struct my_type
{
int i;
template<typename OStream>
friend OStream &operator<<(OStream &os, const my_type &c)
{
return os << "[my_type i=" << c.i << "]";
}
};
void user_defined_example()
{
spdlog::info("user defined type: {}", my_type{14});
}
// Custom error handler. Will be triggered on log failure.
void err_handler_example()
{
// can be set globally or per logger(logger->set_error_handler(..))
spdlog::set_error_handler([](const std::string &msg) {
printf("*** Custom log error handler: %s ***\n", msg.c_str()); });
}
// syslog example (linux/osx/freebsd)
#ifndef _WIN32
#include "spdlog/sinks/syslog_sink.h"
void syslog_example()
{
std::string ident = "spdlog-example";
auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID);
syslog_logger->warn("This is warning that will end up in syslog.");
}
#endif
// Android example.
#if defined(__ANDROID__)
#include "spdlog/sinks/android_sink.h"
void android_example()
{
std::string tag = "spdlog-android";
auto android_logger = spdlog::android_logger_mt("android", tag);
android_logger->critical("Use \"adb shell logcat\" to view this message.");
}
#endif
/*** R
exampleTwo()
*/
```
We are not showing the output here; it can be compiled, linked, loaded and run just as above by simply passing the filename to `Rcpp::sourceCpp()`.
Note that is will create a few demonstration logfiles so you may want to run the example from a temporary directory.
## Third example: Colour
The next example highlights a colour 'sink' for the logger.
Again, do not use this as a starting point for your package as `R CMD check` will protest about use of `stdout`.
```c++
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include <Rcpp.h>
// [[Rcpp::depends(RcppSpdlog)]]
// [[Rcpp::export]]
void exampleThree() {
auto console = spdlog::stdout_color_mt("console");
// change log pattern (changed from [%H:%M:%S %z] [%n] [%^---%L---%$] )
spdlog::set_pattern("[%H:%M:%S.%f] [%^%L%$] [thread %t] %v");
spdlog::info("This an info message with custom format");
//spdlog::set_pattern("%+"); // back to default format
spdlog::info("Welcome to spdlog!");
spdlog::error("Some error message with arg: {}", 1);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:<30}", "left aligned");
// added to this example file allow multiple runs of different package functions
spdlog::drop("console");
}
/*** R
exampleThree()
*/
```
When running this example in a terminal capable of displaying colour escape sequence, the logging levels are distinguished by colour.
This ranges from green ("info") to yellow ("warning") to red ("error") and white-on-red ("critical").
Not that in this vignette color from standard output does how not show (in the keep-it-simple-mode we are using here).
```sh
R> exampleThree()
[14:47:52.260692] [I] [thread 2502026] This an info message with custom format
[14:47:52.260715] [I] [thread 2502026] Welcome to spdlog!
[14:47:52.260732] [E] [thread 2502026] Some error message with arg: 1
[14:47:52.260734] [W] [thread 2502026] Easy padding in numbers like 00000012
[14:47:52.260736] [C] [thread 2502026] Support for int: 42; hex: 2a; oct: 52; bin: 101010
[14:47:52.260739] [I] [thread 2502026] Support for floats 1.23
[14:47:52.260741] [I] [thread 2502026] Positional args are supported too..
[14:47:52.260743] [I] [thread 2502026] left aligned
R>
```
## Fourth Example: Dedicated R Logger
The next example is suitable for use in R packages, and in fact included as an example in the package.
We include the source file [src/exampleRsink.cpp](https://github.com/eddelbuettel/rcppspdlog/blob/master/src/exampleRsink.cpp).
```c++
// this portmanteau include also defines the r_sink we use below, and which
// diverts all logging to R via the Rcpp::Rcout replacement for std::cout
#include <RcppSpdlog>
#include <spdlog/stopwatch.h> // also support stopwatch feature
//' spdlog Example using a sink for R
//'
//' A simple example invoking a derived R/Rcpp logger. Also demonstrates the
//' stopwatch feature. For more features see the 'spdlog' documnetation.
//'
//' Note that this no longer triggers R warnings thanks to excellent help by
//' Gabi Melman.
//' @return None
//' @examples
//' exampleRsink()
// [[Rcpp::export]]
void exampleRsink() {
std::string logname = "fromR"; // fix a name for this logger
auto sp = spdlog::get(logname); // retrieve existing one
if (sp == nullptr) sp = spdlog::r_sink_mt(logname); // or create new one if needed
spdlog::set_default_logger(sp); // and set as default
spdlog::stopwatch sw; // instantiate a stop watch
// change log pattern (changed from [%H:%M:%S %z] [%n] [%^---%L---%$] )
spdlog::set_pattern("[%H:%M:%S.%f] [%n] [%^%L%$] [thread %t] %v");
spdlog::info("Welcome to spdlog!");
spdlog::error("Some error message with arg: {}", 1);
spdlog::info("Elapsed time: {}", sw);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:<30}", "left aligned");
spdlog::info("Elapsed time: {}", sw);
}
//' spdlog Logging Lever Setter
//'
//' A helper function to turn a logging level given as string
//' into the current logging level
//'
//' @param name A string with the logging level. Value understood are,
//' in decreasing verbosity \sQuote{trace}, \sQuote{debug}, \sQuote{info},
//' \sQuote{warning}, \sQuote{error}, \sQuote{critical}, and \sQuote{off}.
//' Unrecognised names are equivalent to \sQuote{off}.
//' @return Nothing is returned.
// [[Rcpp::export]]
void setLogLevel(const std::string &name) {
spdlog::set_level(spdlog::level::from_str(name));
}
```
The example file contains three key aspects to highlight:
- use of the `r_sink_mt()` class for R-specific logger sink
- use of the very convenient `stopwatch` object
- use of logging levels
We highlight these below after first showing the relevant output:
```sh
R> exampleRsink()
[16:52:12.076751] [fromR] [I] [thread 2453030] Welcome to spdlog!
[16:52:12.076809] [fromR] [E] [thread 2453030] Some error message with arg: 1
[16:52:12.076823] [fromR] [I] [thread 2453030] Elapsed time: 9.6104e-05
[16:52:12.076833] [fromR] [W] [thread 2453030] Easy padding in numbers like 00000012
[16:52:12.076844] [fromR] [C] [thread 2453030] Support for int: 42; hex: 2a; oct: 52; bin: 101010
[16:52:12.076853] [fromR] [I] [thread 2453030] Support for floats 1.23
[16:52:12.076871] [fromR] [I] [thread 2453030] Positional args are supported too..
[16:52:12.076879] [fromR] [I] [thread 2453030] left aligned
[16:52:12.076892] [fromR] [I] [thread 2453030] Elapsed time: 0.000167057
R>
```
#### R-specific sink
[spdlog](https://github.com/gabime/spdlog) has the ability to derive and sub-class sinks for logger.
The [RcppSpdlog](https://github.com/eddelbuettel/rcppspdlog) package uses this feature to implement a sink using the Rcpp conduit `Rcpp::Rcout` instead of `std::cout` as it conveniently redirects to
the R output stream.
This class should be the default one for any R packages wanting to use [spdlog](https://github.com/gabime/spdlog) while also passing `R CMD check`.
A second important aspect of the initial code in function `exampleRsink()` is how a named logging instance is requested.
If none is found, a new one is instantiated.
Next, this logger is made the default logger permitting convenient programmatic access via `spdlog::`.
#### Stopwatch
A powerful (recent) feature of the include [fmt](https://github.com/fmtlib/fmt) library is the automatic formatting of timestamps and interval.
As the code example shows, simply instantianting an object, here called `sw`, and referring to it later is all that takes.
#### Log-Level
A second utility function `setLogLevel()` is also provided.
Usage is simple: after calling it with a given level, only message equal to it or higher are shown as the next example shows.
```sh
R> setLogLevel("error")
R> exampleRsink()
[16:54:12.666261] [fromR] [E] [thread 2453030] Some error message with arg: 1
[16:54:12.666286] [fromR] [C] [thread 2453030] Support for int: 42; hex: 2a; oct: 52; bin: 101010
R>
```
By requestion level 'error', message of level 'info', 'warning' or 'debug' are suppressed but messages levels 'error' or 'critical' as shown as desired.
## Fifth Example: Initialization
Package desiring to use [RcppSpdlog](https://github.com/eddelbuettel/rcppspdlog) can initialize its facilities during startup.
For that we first define a setup function
```c++
// this portmanteau include also defines the r_sink we use below, and which
// diverts all logging to R via the Rcpp::Rcout replacement for std::cout
#include <RcppSpdlog>
//' Set a new default logger for R
//'
// [[Rcpp::export]]
void setDefault() {
std::string logname = "fromR"; // fix a name for this logger
auto sp = spdlog::get(logname); // retrieve existing one
if (sp == nullptr) sp = spdlog::r_sink_mt(logname); // or create new one if needed
sp->set_pattern("[%H:%M:%S.%f] [%^%L%$] %v");
spdlog::set_default_logger(sp);
}
```
We can then call this function during startup:
```r
.onLoad <- function(libname, pkgname) {
setDefault();
}
```
## Sixth Example: Compile-time Selection
Of course, [spdlog](https://github.com/gabime/spdlog) also supports a common
usage paradigm with loggers in which the decision of whether to log or not is
_compile-time_ rather than run time. As this is typically implemented via
macros, usage is via upper-case macros as well.
The following example shows a function with three different logging-level
statements as well as a `#define` set such one and only one is
shown. Similarly, code can contain debug or trace or info or ... statements
which would _not_ appear in the actually loaded "production code" (or CRAN
version) if the compile-time logging level define is set high enough.
```c++
// this portmanteau include also defines the r_sink we use below, and which
// diverts all logging to R via the Rcpp::Rcout replacement for std::cout
#include <RcppSpdlog>
// A define such as this could also be set in src/Makevars via a -D flag
#define SPDLOG_LOG_LEVEL SPDLOG_LEVEL_CRITICAL
// [[Rcpp::export]]
void demoInvisible() {
Rcpp::Rcout << "Hello from demoInvisible, just to show we're being called...\n";
// trace message via default logger
SPDLOG_TRACE("Some trace message.. {} ,{}", 1, 3.23);
// debug message via default logger
SPDLOG_DEBUG("Some debug message.. {} ,{}", 1, 3.23);
// debug message via default logger
SPDLOG_CRITICAL("Some critical message.. {} ,{}", 1, 3.23);
}
```
When a piece of code with such compile-time defines is used, we see the
expected outcome. The following example uses default logger, and as the
preceding section showed this can be set up to be the custom R sink:
```r
R> Rcpp::sourceCpp("/tmp/rcppspdlog.cpp") # plus a '// [[Rcpp:depends("RcppSpdlog")'
R> demoInvisible()
Hello from demoInvisible, just to show we're being called...
[08:44:48.198075] [fromR] [C] [thread 2453030] Some critical message.. 1 ,3.23
R>
```
## Seventh Example: Access From R
As of package 0.0.9, [RcppSpdlog](https://github.com/eddelbuettel/rcppspdlog)
supports two new modes. The first is direct logging support from R and
described in this section; the second is access from another R package and
described thereafter. A number of basic functions are
exported using Rcpp. These include `log_setup(name, level)` to instantiate a
named logger at a given level (instead of an unnamed default at level
'warn'), `log_filesetup(filename, name, level)` (same using the named file as
logging destination), a helper `log_drop(name)` to drop a named logger, two setters
`log_set_pattern()` and `log_set_level()` to set, respectively, the displayed
log pattern and the level. This is complemented by the actual loggers ranging
from `log_trace()` and `log_debug()` to `log_info()`, `log_warn()`,
`log_error()` and finally `log_critical()`.
The following example (also the example in the manual page) illustrates.
```r
> library(RcppSpdlog)
> log_setup("demo") # default level 'warn' is used
> log_info("this message is NOT seen")
> log_set_level("debug")
> log_set_pattern("%^[%H:%M:%S.%e] [%n] [%l] %v%$") # set a pattern w/o process id
> log_info("this message is seen")
[15:55:34.150] [demo] [info] this message is seen
> log_warn("as is this message")
[ 15:55:37.513] [demo] [warning] as is this message
>
```
The interface expects a character value so use from either `sprintf()` or a
string-interpolating helper such as `glue::glue` can be used:
```r
> log_info(sprintf("We can %s a %s with values %d", "build", "text", 42L))
[16:03:37.728] [demo] [info] We can build a text with values 42
> log_info(glue::glue("We can {a} a {b} with values {v}", a="build", b="text", v=42L))
[16:03:46.395] [demo] [info] We can build a text with values 42
>
```
## Eight Example: Access From Another R Package
As of package 0.0.9, another package can use the C++ level functions (either
with or without the R functions) by importing the `RcppSpdlog` while ensuring
at least one function from the package is imported (so that the C-level
interface functions are instantiated by R). This is time-honoured mechanism
long-used by `lme4` to access (compiled) functions from `Matrix` as well as
by `xts` to access code from `zoo`, and others.
To properly import the package, add just one import, for example
`importFrom((RcppSpdlog, log_setup)` to the `NAMESPACE` file of your package,
along with the required `Imports: RcppSpdlog` in the `DESCRIPTION` file. The
available functions are the same as the ones described in the previous
section, but now available at the C++ level in the `RcppSpdlog` namespace. So
for example
```c++
#include <RcppSpdlog.h>
RcppSpdlog::log_setup("demoLogger", "info"); // create logger at info level
RcppSpdlog::log_info("logger created");
```
will work.
## Nineth Example: More compact C++ Access
As the (auto-generated, thanks to `Rcpp`) interface described in the previous
section is a little "wordy", we added a simple aliasing wrapping in a new
namespace `spdl` and, given the protection from naming collisions offered by
the namespace, shortened the accessor function names. So the previous
example can also be used via
```c++
#include <spdl.h>
spdl::setup("demoLogger", "info"); // create logger at info level
spdl::info("logger created");
```
The logger interface takes a simple string. Two easy options exist for
formatting such as string. First, one can rely on the
[tinyformat](https://github.com/c42f/tinyformat) version included with `Rcpp`
and use `tfm::format()` which works with standard `printf()` operators.
Second, one can use the `fmt` library included with `spdlog` via an explicit call.
```c++
# using tfm::format
spdl::info(tfm::format("We %s values %d and %f", "log", 42, 1.23));
# using fmt::format
spdl::info(fmt::format("We {} values {} and {}", "log", 42, 1.23));
```
Here both formatters have to be called explicitly as we use a simple
one-function signature (per logging function) to the underlying C language
implementation without the fuller flexibility of variadic arguments.
As C++11 can be assumed, we can also offers a variadic template expansion for
`fmt::format()` and the second example simply becomes
```c++
spdl::info("We {} values {} and {}", "log", 42, 1.23);
```
## Tenth Example: More compact R Access
As the more compact access in the previous section is quite compelling we
also created a sibbling R package [spd](https://github.com/eddelbuettel/spdl)
providing a `spdl` namespace in R allowing _the exact same format strings too`.
So
```r
spdl::info("We {} values {} and {}", "log", 42L, 1.23);
```
now also works from R using the same formatting string. We inted to upload `spdl` to CRAN too.
Note that other all other formatting options are supported from R: the first argument is a character
variable which can be constructed using `paste`, `sprintf`, or any of the string-interpolating
packages. But as none of those methods works like [fmt](https://github.com/fmtlib/fmt) (which we
have come to like a lot) we added support for it too.
## Eleventh Example: Stopwatch Support in R and C++
As shown above, [spdlog](https://github.com/gabime/spdlog) supports a 'stopwatch' in C++. Usage is
straightforward: one first instantiates an object of type `stopwatch` (make sure to first include
the appropriate header too!) and then reports elapsed time by including the `stopwatch` object in
the logger.
We wrap this `spdlog::stopwatch` object in an external pointer to make it accessible from
R. Moreover, by creating it as a S3 object we can add a `format()` method to make the usage pattern
identical to the use in C++. So in R we now have
```r
sw <- RcppSpdlog::get_stopwatch()
Sys.sleep(0.2)
RcppSpdlog::log_warn("Elapsed time via stopwatch: {}", sw)
```
This also works in C++.
```c++
auto sw = RcppSpdlog::get_stopwatch(); // returns XPtr<spdlog::stopwatch
usleep(200); // from unistd.h
RcppSpdlog::log_warn("Elapsed", RcppSpdlog::format_stopwatch(sw));
```
Note that in that last line we need to _explicitly_ call a formatter as we do not get the templated
formatter from [spdlog](https://github.com/gabime/spdlog) as our object is an external-pointer
wrapped object.
We also export this via the `spdl.h` wrapper. So the same C++ functionality is also available
as
```c++
auto sw = spdl::stopwatch(); // returns XPtr<spdlog::stopwatch
usleep(200); // from unistd.h
spdl::warn("Elapsed", spdl::format(sw));
```
And because the [spdl](https://cran.r-project.org/package=spdl) package wraps this for R, we can do
the same in R:
```r
sw <- spdl::stopwatch()
Sys.sleep(0.2)
spdl::warn("Elapsed: {}", sw)
```
which once again takes advantage of the S3 class and its `format()` method.
## Conclusion
[spdlog](https://github.com/gabime/spdlog) and the included [fmt](https://github.com/fmtlib/fmt) are
two very powerful and widely used C++ libraries. The
[RcppSpdlog](https://github.com/eddelbuettel/rcppspdlog) package adds to them to the set of packages
R users can deploy. The [spd](https://github.com/eddelbuettel/spdl) package makes access even easier
and more consistent. It is our hope that the examples shown here are of interest to R users who are
looking for effortless, performant and flexible logging solutions for their R packages.