Rust Audio Experiments: 1 - Logging
How to add logging to Rust VST plug-ins
This is (hopefully) the first in a series of posts I want to write while I try to use Rust for audio development. In this post I’d like to share some thoughts around building Rust audio plugins locally.
I’ll show:
- The set-up & libraries for logging from a VST to a file
Logging from the VST
Audio plug-ins will be loaded into a host application as a dynamic library and because of this, logging to standard output will not be effective (as the host’s standard output is not available while doing manual testing).
In order to log from the VST, we’ll log to a file.
Rust logging facade
Rust has the standard log
crate to be used as
a logging facade. Logs can be added to the code as follows:
fn main() {
log::error!("Error msg");
log::warn!("Warning msg");
log::info!("Info msg");
log::debug!("Debug msg");
log::trace!("Trace msg");
}
Adding log
statements to the code will not output anywhere and needs a logger
implementation to be configured.
log4rs set-up to log to a file
For this use-case I’ve found log4rs
provided a simple enough set-up. The
plug-in will call a configure_logging()
function, which will try to initialize
the logger.
fn configure_logging(root_config_path: &Path, name: &str) { /* ... */ }
Log destination
We’ll write logs to two destinations:
- Standard output
- A log file
We’ll rotate the log file when it reaches a certain size and ensure the destination directory exists before starting.
Creating the root configuration/logging directory
Some other part of our application will give us the logging directory, which may
be in a .
directory of the user’s HOME or, for example, in
/Library/Application Support
.
The ensure_logging_directory
function will make sure the root path exists:
fn ensure_logging_directory(root_config_path: &Path) -> Result<PathBuf> {
std::fs::create_dir_all(root_config_path)
.map_err(LoggingSetupError::CreateLogDirectoryError)?;
Ok(root_config_path.to_path_buf())
}
It returns a Result
, which is a type I’ll define soon.
Configuring logging
To configure logging we’ll set up two Appenders
which log to stdout & the file:
pub fn configure_logging(root_config_path: &Path, name: &str) -> Result<()> {
let log_dir = ensure_logging_directory(root_config_path)?;
let log_path = log_dir.join(name);
let logfile = RollingFileAppender::builder()
.encoder(Box::new(PatternEncoder::new(
"{d} [{l}] {M}:{L} - {m} - tid:{T}:{t} pid:{P}\n",
)))
.build(
log_path,
Box::new(CompoundPolicy::new(
Box::new(SizeTrigger::new(1024 * 1024 * 10)),
Box::new(DeleteRoller::new()),
)),
)
.map_err(LoggingSetupError::FileAppenderError)?;
let config = Config::builder()
.appender(Appender::builder().build("logfile", Box::new(logfile)))
.appender(Appender::builder().build("stdout", Box::new(ConsoleAppender::builder().build())))
.build(
Root::builder()
.appender("logfile")
.appender("stdout")
.build(LevelFilter::Info),
)?;
log4rs::init_config(config)?;
Ok(())
}
Errors
We’ll use thiserror
to handle errors. It’ll join the 4 types of errors that
may happen during this process into one type:
#[derive(thiserror::Error, Debug)]
pub enum LoggingSetupError {
#[error("Failed to create logging directory")]
CreateLogDirectoryError(std::io::Error),
#[error("Failed to set-up log-file appender")]
FileAppenderError(std::io::Error),
#[error("Failed to set-up logging configuration")]
LogConfigError(#[from] ConfigErrors),
#[error("Failed to set logger configuration")]
SetLoggerError(#[from] SetLoggerError),
}
pub type Result<T> = std::result::Result<T, LoggingSetupError>;