1

This code uses tracing events:

# Cargo.toml
[dependencies]
tracing = "0.1.3"
tracing-subscriber = { version = "0.2.9", features = ["chrono", "env-filter", "fmt"] }
tracing-appender = "0.1.1"
use tracing::{Level, event, };
use tracing::dispatcher::{with_default, Dispatch};
use std::thread;
use tracing_appender::rolling::{RollingFileAppender};
use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
use tracing_subscriber::fmt::SubscriberBuilder;

pub static file_appender:RollingFileAppender = tracing_appender::rolling::never("/ws/sarvi-sjc/", "prefix.log");
pub static (non_blocking, _guard:WorkerGuard):(NonBlocking:WorkerGuard) = tracing_appender::non_blocking(file_appender);
pub static subscriber:SubscriberBuilder = tracing_subscriber::FmtSubscriber::builder()
    .with_max_level(Level::TRACE)
    .with_writer(non_blocking)
    .finish();
pub static my_dispatch = Dispatch::new(subscriber);

with_default(&my_dispatch, || {
    event!(Level::INFO, "chmod(...)");
});

I want the first global static lines to be initialized and stored in a thread_local!() so that it is initialized only once for each thread.

I should then be able to use that thread-specific Dispatch instance to scope event subscribers. The above code is taken from inside a function and were let statements. As static variables, one them doesn't work, and had the same problem within thread_local!() as well.

pub static (non_blocking, _guard:WorkerGuard):(NonBlocking:WorkerGuard) = tracing_appender::non_blocking(file_appender);
error: expected identifier, found `(`
  --> src/lib.rs:13:12
   |
13 | pub static (non_blocking, _guard:WorkerGuard):(NonBlocking:WorkerGuard) = tracing_appender::non_blocking(file_appender);
   |            ^ expected identifier

The second problem was understanding how they are initialized in a thread local fashion.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
Sarvi Shanmugham
  • 487
  • 4
  • 13
  • Thanks for looking into it. I did what you suggested. ran the code through the playground. – Sarvi Shanmugham Jul 28 '20 at 20:03
  • 1
    By the way, idiomatic Rust uses `snake_case` for variables, methods, macros, fields and modules; `UpperCamelCase` for types and enum variants; and `SCREAMING_SNAKE_CASE` for statics and constants. – Shepmaster Jul 28 '20 at 20:19
  • Sorry. I come from the python world. Will take a bit of time adjusting to the switch – Sarvi Shanmugham Jul 28 '20 at 21:17

1 Answers1

2

Wrap your static declarations with the thread_local! macro, then you can access each value using the with method, which will return a value unique to the thread.

use tracing::{
    dispatcher::{with_default, Dispatch},
    event, Level,
};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::FmtSubscriber;

fn make_dispatch() -> (Dispatch, WorkerGuard) {
    let file_appender = tracing_appender::rolling::never("ws/sarvi-sjc/", "prefix.log");
    let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
    let subscriber = FmtSubscriber::builder()
        .with_max_level(Level::TRACE)
        .with_writer(non_blocking)
        .finish();
    (Dispatch::new(subscriber), guard)
}

thread_local!(static MY_DISPATCH: (Dispatch, WorkerGuard) = make_dispatch());

fn main() {
    // Main thread:
    let (my_dispatch, _guard) = make_dispatch();
    with_default(&my_dispatch, || {
        event!(Level::INFO, "main thread");
    });

    // Other thread:
    std::thread::spawn(|| {
        MY_DISPATCH.with(|(my_dispatch, _guard)| {
            with_default(&my_dispatch, || {
                event!(Level::INFO, "other thread");
            });
        });
    })
    .join()
    .unwrap();
}

I made sure to store the WorkerGuard in thread local storage too, so that it does not go out of scope after MY_DISPATCH is initialized. This is because the documentation for tracing_appender::non_blocking states:

This function returns a tuple of NonBlocking and WorkerGuard. NonBlocking implements MakeWriter which integrates with tracing_subscriber. WorkerGuard is a drop guard that is responsible for flushing any remaining logs when the program terminates.

Note that the WorkerGuard returned by non_blocking must be assigned to a binding that is not _, as _ will result in the WorkerGuard being dropped immediately. Unintentional drops of WorkerGuard remove the guarantee that logs will be flushed during a program's termination, in a panic or otherwise.

This way, the guard will be dropped when the thread exits. However, keep in mind that Rust's built-in thread local storage has some weird quirks about initialization and destruction. See the documentation for std::thread::LocalKey. Notably:

On Unix systems when pthread-based TLS is being used, destructors will not be run for TLS values on the main thread when it exits. Note that the application will exit immediately after the main thread exits as well.

Therefore, on the main thread, you should call make_dispatch() directly rather than MY_DISPATCH, so that it is dropped when the program exits (but note that in general, things are not guaranteed to be dropped, especially during a panic or std::process::exit, etc.; therefore, there is still a chance that some logs could be lost, as is the nature of most non-blocking I/O).

Coder-256
  • 5,212
  • 2
  • 23
  • 51
  • Thanks. This works great in a small test. I havent tested multi threading yet. I have question. you suggest calling make_dispatch in the main thread. Except that the program I am writing does not have access to main. It is a LD_PRELOAD library that gets loaded via LD_PREOAD to other programs that may not not even be rust. Will there be an issue? Should I try to check if this the main thread and do it differently? – Sarvi Shanmugham Jul 29 '20 at 04:04
  • Glad I could help! I wish I could help more but I honestly have no clue. I am not even sure if any of this is safe in the scenario you describe (make sure to always use `catch_unwind`!). Aside from that, my main advice is to simply avoid using the non-blocking writer if it is important that you do not miss any log messages (maybe you could simply wrap the file appender in a closure instead of calling `tracing_appender::non_blocking`), however that will come at the cost of performance. – Coder-256 Jul 29 '20 at 06:14