Keeping Configuration Out of Your Code
redrop::set_concurrency_limit(8) is a perfectly normal function call, and you could just write
it directly in main. For a lot of small programs, that's genuinely fine — don't over-engineer
this if you don't need to.
But the concurrency limit is exactly the kind of value that tends to need retuning without a code change: what's right for your laptop during development is probably wrong for a production server, and what's right today might be wrong once traffic grows. Recompiling and redeploying just to change one number is friction you don't need. So the showcase crate keeps it in a small external file instead, and loads it at startup.
redrop.toml
Sitting next to showcase/src/main.rs, there's a redrop.toml:
[collector]
concurrency_limit = 8
That's the entire tuning surface, in one place, in a format anyone on the team can edit without reading Rust.
Loading it
We describe the shape we expect with a couple of small serde structs:
use serde::Deserialize;
#[derive(Deserialize)]
struct RedropConfig {
collector: CollectorConfig,
}
#[derive(Deserialize)]
struct CollectorConfig {
concurrency_limit: usize,
}
and read it once, at the top of main:
fn load_config(path: &std::path::Path) -> RedropConfig {
let text = std::fs::read_to_string(path)
.unwrap_or_else(|err| panic!("couldn't read {}: {err}", path.display()));
toml::from_str(&text)
.unwrap_or_else(|err| panic!("couldn't parse {}: {err}", path.display()))
}
We go one small step further and let the path itself be overridden by an environment variable, falling back to the file shipped next to the crate:
fn config_path() -> std::path::PathBuf {
std::env::var_os("REDROP_CONFIG")
.map(std::path::PathBuf::from)
.unwrap_or_else(|| {
std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/redrop.toml")).to_owned()
})
}
That's what makes this genuinely "configuration elsewhere" rather than just "a constant in a
different file": the same compiled binary can pick up different tuning per environment —
a redrop.toml baked into a container image, a path mounted from a config map, whatever your
deployment looks like — without anyone touching main.rs.
Then, applying it is one line, right where we saw it in chapter 4:
let config = load_config(&config_path());
redrop::set_concurrency_limit(config.collector.concurrency_limit);Try changing it
REDROP_CONFIG=/tmp/my-redrop.toml cargo run -p redrop-showcase
Drop the concurrency_limit to 1 in a copy of the file and re-run — you'll see the flushes in
the output stop overlapping and run strictly one at a time. Nothing about LogShipper or its
async_drop changed; only the number in the file did.