Skip to main content

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:

src/main.rs
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):

config/app.yaml
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.)

src/main.rs
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:

config/app.prod.yaml
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)"}