#![allow(missing_docs)]
use anyhow::Result;
use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Deserialize, Serialize)]
#[serde(default)]
struct Config {
log_level: librebar::config::LogLevel,
tick_interval_ms: u64,
shutdown_grace_ms: u64,
greeting: String,
}
impl Default for Config {
fn default() -> Self {
Self {
log_level: librebar::config::LogLevel::Info,
tick_interval_ms: 1000,
shutdown_grace_ms: 500,
greeting: "service up".to_string(),
}
}
}
#[derive(Parser)]
#[command(name = "service", about = "Long-running librebar service example")]
struct Cli {
#[command(flatten)]
common: librebar::cli::CommonArgs,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Subcommand)]
enum Command {
Run,
Info,
Crash,
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let cli = Cli::parse();
cli.common.apply_color();
cli.common.apply_chdir()?;
let app = librebar::init("service")
.with_version(env!("CARGO_PKG_VERSION"))
.with_cli(cli.common)
.config::<Config>()
.logging()
.otel()
.shutdown()
.crash_handler()
.start()?;
match cli.command.unwrap_or(Command::Info) {
Command::Run => run(&app).await,
Command::Info => {
print_info(&app);
Ok(())
}
Command::Crash => {
tracing::error!(phase = "pre-panic", "demonstrating crash handler");
panic!("intentional panic to demonstrate crash handler");
}
}
}
async fn run(app: &librebar::App<Config>) -> Result<()> {
let config = app.config();
let mut token = app
.shutdown_token()
.expect("shutdown enabled on the builder");
let mut ticker = tokio::time::interval(Duration::from_millis(config.tick_interval_ms));
ticker.tick().await;
println!(
"{}: ticking every {}ms; Ctrl-C to stop",
config.greeting, config.tick_interval_ms
);
tracing::info!(
tick_interval_ms = config.tick_interval_ms,
shutdown_grace_ms = config.shutdown_grace_ms,
"service started",
);
let mut ticks: u64 = 0;
loop {
tokio::select! {
_ = ticker.tick() => {
ticks += 1;
tracing::debug!(tick = ticks, "work tick");
}
_ = token.cancelled() => {
tracing::info!(ticks, "shutdown signal received");
break;
}
}
}
tokio::time::sleep(Duration::from_millis(config.shutdown_grace_ms)).await;
tracing::info!(ticks, "service stopped cleanly");
println!("stopped cleanly after {ticks} ticks");
Ok(())
}
fn print_info(app: &librebar::App<Config>) {
let config = app.config();
let otel_endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT")
.ok()
.filter(|v| !v.is_empty());
println!("app: {} v{}", app.app_name(), app.version());
println!("sources: {:?}", app.config_sources());
println!(
"log dir: {:?}",
librebar::logging::platform_log_dir(app.app_name())
);
println!(
"crashes: {}",
librebar::crash::crash_dump_dir(app.app_name()).display()
);
match otel_endpoint {
Some(ep) => println!("otel: exporting to {ep}"),
None => println!("otel: disabled (set OTEL_EXPORTER_OTLP_ENDPOINT to enable)"),
}
println!(
"config: tick={}ms grace={}ms greeting={:?}",
config.tick_interval_ms, config.shutdown_grace_ms, config.greeting
);
}