Quickstart
Let’s learn Strut with an example.
Strut dependency
Create a binary crate and add the strut dependency:
cargo new demo-app && cd demo-app
cargo add strut
Write a simple main function:
use strut::AppConfig;
#[strut::main]
async fn main() {
let config = AppConfig::get();
println!("Running {}...", config.name());
}
Run the application with cargo run:
$ cargo run Running app...
Impressive, right? Just kidding, it gets better.
The application name (same as everything in AppConfig) has a reasonable default.
Add configuration
Let’s configure the name explicitly (under the hood, the config crate is used):
- YAML
- TOML
name: app-backend
name = "app-backend"
Now, when we cargo run, the output is:
$ cargo run Running app-backend...
Override configuration
We can also set/override all config entries with environment variables:
APP_NAME=app-custom cargo run
$ APP_NAME=app-custom cargo run Running app-custom...
Nothing too exciting so far.
Example component
The bulk of Strut’s usefulness comes from components. Components are enabled via feature flags.
Add tracing component
Most applications benefit from logging and tracing.
The tracing feature pulls and pre-configures the tracing crate.
cargo add strut --features tracing
Change the main function to use tracing macros instead.
(We don’t depend on the tracing crate directly, but Strut re-exports important APIs for us.)
use strut::AppConfig;
use strut::tracing::info;
#[strut::main]
async fn main() {
let config = AppConfig::get();
info!("Running {}...", config.name());
}
Now, cargo run outputs something more serious, and perhaps familiar:
$ cargo run 1969-10-29T22:30:00.010227Z INFO ThreadId(01) strut::launchpad::wiring::preflight: Starting app-backend with profile 'dev' (default replica, lifetime ID 'xbr-dwxn-klp') 1969-10-29T22:30:00.010296Z INFO ThreadId(01) demo_app: Running app-backend... 1969-10-29T22:30:00.010308Z INFO ThreadId(01) strut_core::context: Terminating application context 1969-10-29T22:30:00.010325Z INFO ThreadId(01) strut_core::spindown::registry: Spindown initiated 1969-10-29T22:30:00.010352Z INFO ThreadId(01) strut_core::spindown::registry: Spindown completed
A few things to notice:
- Strut maintains an application context which is terminated on shutdown.
- A spindown procedure is baked in, where long-running services have a chance to free resources.
Add prod configuration
The tracing output is nice and human-readable.
But in prod we almost certainly need structured logging.
Let’s add that:
cargo add strut --features tracing-json
Add a prod-specific config file following the naming convention:
- YAML
- TOML
tracing:
flavor: json # “flavour” works too :)
[tracing]
flavor = "json" # “flavour” works too :)
Nothing will change for cargo run.
But when we set the runtime profile to prod:
APP_PROFILE=prod cargo run
The output is now fit for streaming structured logs:
$ APP_PROFILE=prod cargo run {"timestamp":"1969-10-29T22:30:00.937059Z","level":"INFO","fields":{"message":"Starting app-backend with profile 'prod' (default replica, lifetime ID 'rli-dgff-ysd')"},"target":"strut::launchpad::wiring::preflight","threadId":"ThreadId(1)"} {"timestamp":"1969-10-29T22:30:00.937169Z","level":"INFO","fields":{"message":"Running app-backend..."},"target":"strut_dummy","threadId":"ThreadId(1)"} {"timestamp":"1969-10-29T22:30:00.937178Z","level":"INFO","fields":{"message":"Terminating application context"},"target":"strut_core::context","threadId":"ThreadId(1)"} {"timestamp":"1969-10-29T22:30:00.937186Z","level":"INFO","fields":{"message":"Spindown initiated"},"target":"strut_core::spindown::registry","threadId":"ThreadId(1)"} {"timestamp":"1969-10-29T22:30:00.937200Z","level":"INFO","fields":{"message":"Spindown completed"},"target":"strut_core::spindown::registry","threadId":"ThreadId(1)"}