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)"}