Rust Audio Experiments: 1 - Logging

How to add logging to Rust VST plug-ins

Author profile picture
Pedro Tacla Yamada
16 Jul 20213 min read

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>;

Source code

See augmented-audio/crates/audio-plugin-logger.