Skip to content

Latest commit

 

History

History
139 lines (111 loc) · 4.47 KB

File metadata and controls

139 lines (111 loc) · 4.47 KB

Log

Signal handling

use std::collections::HashMap;
//use std::env;

use tracing::info;
use tracing::span;
use tracing::trace;
use tracing::Instrument;
use tracing::Level;
use tracing_subscriber::EnvFilter;

//use std::io::Error;

use signal_hook::consts::signal::*;
use signal_hook_tokio::Signals;

use futures::stream::StreamExt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

#[derive(Debug)]
struct Context {
    variables: HashMap<String, String>,
    dry_run: bool,
}

impl Context {
    async fn from_path(path: &str) -> anyhow::Result<Self> {
        let mut variables = HashMap::new();
        tracing::error!("Creating context");
        info!("Creating context from path: {}", path);
        tracing::debug!("Creating context");
        trace!(?path);

        // test span again
        // If log level is info, process_recipe() prints log message like
        // 2025-02-23T15:10:29.315933Z  INFO cook{path="/path/to/file"}: rust_log: process_recipe::Ingredients: ["Pasta", "Eggs", "Bacon", "Parmesan"]
        // If log level is debug, process_recipe() prints log message like
        // 2025-02-23T15:09:40.495049Z  INFO my_span_main{path="/path/to/file"}:cook{path="/path/to/file"}: rust_log: process_recipe::Ingredients: ["Pasta", "Eggs", "Bacon", "Parmesan"]
        // because the span of "my_span_main" is added for the debug level.
        let span = span!(Level::INFO, "cook", ?path);
        process_recipe().instrument(span).await?;

        variables.insert("path".to_owned(), path.to_owned());
        Ok(Self {
            variables,
            dry_run: false,
        })
    }
}

async fn process_recipe() -> anyhow::Result<()> {
    let recipe = "Pasta Carbonara";
    let ingredients = vec!["Pasta", "Eggs", "Bacon", "Parmesan"];
    let steps = vec![
        "Cook the pasta",
        "Fry the bacon",
        "Mix the eggs and cheese",
        "Combine everything",
    ];

    tracing::error!("process_recipe::Recipe: {}", recipe);
    tracing::info!("process_recipe::Ingredients: {:?}", ingredients);
    tracing::debug!("Steps: {:?}", steps);

    Ok(())
}

async fn handle_signals(mut signals: Signals, term: Arc<AtomicBool>) {
    while let Some(signal) = signals.next().await {
        match signal {
            SIGHUP => {
                // Reload configuration
                // Reopen the log file
                println!("SIGHUP received");
            }
            SIGTERM | SIGINT | SIGQUIT => {
                // Shutdown the system;
                println!("SIGTERM, SIGINT, or SIGQUIT received");
                term.store(true, Ordering::Relaxed);
            }
            _ => unreachable!(),
        }
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    println!("Hello, world!");

    // error,rust_log=info: rust_log crate can print info-level log messages, others can print only error level
    // But env RUST_LOG can override this setting, for example) RUST_LOG=debug cargo run
    let envfilter = EnvFilter::builder()
        .try_from_env()
        .unwrap_or_else(|_| EnvFilter::new("error,rust_log=info")); // try with rust_log=debug and rust_log=info to test two span! calls
    tracing_subscriber::fmt().with_env_filter(envfilter).init();

    tracing::error!("This is an error message");
    tracing::info!("This is an info message"); // This is not printed because the filter is set to info.
    tracing::debug!("This is a debug message"); // This is not printed because the filter is set to info.

    let path = "/path/to/file".to_owned();

    // ADD "my_span_main{path="/path/to/file"}" to the log message if the log level is DEBUG
    let span = span!(Level::DEBUG, "my_span_main", ?path);

    let context = Context::from_path(&path).instrument(span.clone()).await?;
    tracing::info!("info level message context={:?}", context);
    tracing::debug!("debug message");

    let signals = Signals::new(&[SIGHUP, SIGTERM, SIGINT, SIGQUIT])?;
    let handle = signals.handle();

    let term = Arc::new(AtomicBool::new(false));
    let signals_task = tokio::spawn(handle_signals(signals, term.clone()));

    // Execute your main program logic

    while !term.load(Ordering::Relaxed) {
        // Do some time-limited stuff here
        // (if this could block forever, then there's no guarantee the signal will have any
        // effect).
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        println!("sleeping");
    }

    // Terminate the signal stream.
    handle.close();
    signals_task.await?;

    Ok(())
}