I want to initialize a tracing registry with multiple outputs. But the outputs is dynamical for different environments.
I can't using chain init like this Registry::default().with().with().init().
When I init registry via for-loop, the registry is moved.
I don't know how to impl it. Can anyone help me?
The code is in the following.
use std::str::FromStr;
use serde_json::json;
use tracing::Level;
use tracing_appender::{non_blocking, rolling};
use tracing_subscriber::{
fmt::{layer, writer::MakeWriterExt},
layer::SubscriberExt,
util::SubscriberInitExt,
Registry,
};
fn main() {
// How can I init registry dynamically with multiple outputs?
let log_file_name = "hello";
let registry = Registry::default();
// the log config
// there may be multiple outputs with different log level for different env
// the config will come from outer in the future, and I don’t know the length now
let cfgs = json!([
{ "level": "debug", "output": "stdout" }, // output to `stdout` with level debug
{ "level": "error", "output": "stderr" }, // output to `stderr` with level error
{ "level": "info", "output": "log" } // output to `log` dir with level info
]);
let cfgs = cfgs.as_array().unwrap();
for cfg in cfgs {
let output = cfg.get("output").unwrap().as_str().unwrap();
let level = cfg.get("level").unwrap().as_str().unwrap();
let level = Level::from_str(level).unwrap();
if output == "stdout" { // to stdout
let stdout_layer = layer().with_writer(std::io::stdout.with_max_level(level));
// !!! registry moved
registry.with(stdout_layer)
} else if output == "stderr" { // to stderr
let stderr_layer = layer().with_writer(std::io::stderr.with_max_level(level));
// !!! registry moved
registry.with(stderr_layer)
} else { // otherwise, to a dir
let file_appender = rolling::daily(output, format!("{log_file_name}.log"));
let (non_blocking_appender, _guard) = non_blocking(file_appender);
let file_layer = layer().json().with_writer(non_blocking_appender.with_max_level(level));
// !!! registry moved
registry.with(file_layer)
}
}
registry.init();
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
tracing::info!(a_bool = true, answer = 42, message = "hello tracing");
}
let cfgs = json!([
{ "level": "debug", "output": "stdout" }, // output to `stdout` with level debug
{ "level": "error", "output": "stderr" }, // output to `stderr` with level error
{ "level": "info", "output": "log" } // output to `log` dir with level info
]);
For another env, the cfgs may be the following after parsed.
let cfgs = json!([
{ "level": "info", "output": "stdout" }, // output to `stdout` with level info
{ "level": "info", "output": "/app/log" } // output to `/app/log` dir with level info
]);
to reduce overhead, the Layered structs are strongly typed, so the SubscriberExt::with() method consumes self (impl Subscriber) and returns Layered<L, Self, Self> (another impl Subscriber but different type).
to do what you described, you must erase the type of all the layers, then you can combine those layers together into a Layered<Box<dyn Layer>, Box<dyn Layer>, Registry>, and add the combined layer to the registry.
Thanks for your answer.
I tried the solution, and the code is compiled ok.
But something strange occurred, the file output does not write anything but only create a log file.
The code is in the following.
use std::str::FromStr;
use serde_json::json;
use tracing::Level;
use tracing_appender::{non_blocking, rolling};
use tracing_subscriber::{
fmt::{layer, writer::MakeWriterExt},
layer::SubscriberExt,
util::SubscriberInitExt,
Layer, Registry,
};
fn main() {
// I create layer via `Layer::boxed()` and `Layer::and_then()`.
// The code following compiled ok.
// However, as the following example, threre are three outputs, the `stdout` and `stderr` is ok while the `file` output does't wirte the log but only create a file `log/hello.llog`
let log_file_name = "hello";
let registry = Registry::default();
// the log config
// there may be multiple outputs with different log level for different env
let cfgs = json!([
{ "level": "debug", "output": "stdout" }, // output to `stdout` with level debug
{ "level": "warn", "output": "stderr" }, // output to `stderr` with level error
{ "level": "debug", "output": "log" } // output to `log` dir with level info
]);
let cfgs = cfgs.as_array().expect("log config should be valid");
let mut layers: Option<Box<dyn Layer<Registry> + Sync + Send>> = None;
for cfg in cfgs {
let output = cfg.get("output").unwrap().as_str().unwrap();
let level = cfg.get("level").unwrap().as_str().unwrap();
let level = Level::from_str(level).unwrap();
if output == "stdout" {
// to stdout
let stdout_layer = layer()
.with_writer(std::io::stdout.with_max_level(level))
.with_thread_names(true)
.with_line_number(true);
layers = Some(match layers {
Some(layers) => layers.and_then(stdout_layer).boxed(),
None => stdout_layer.boxed(),
});
} else if output == "stderr" {
// to stderr
let stderr_layer = layer().with_writer(std::io::stderr.with_max_level(level));
layers = Some(match layers {
Some(layers) => layers.and_then(stderr_layer).boxed(),
None => stderr_layer.boxed(),
});
} else {
// otherwise, to a dir
// FIXME!!! NOT WRITE ANYTHING
let file_appender = rolling::never(output, format!("{log_file_name}.log"));
let (non_blocking_appender, _guard) = non_blocking(file_appender);
let file_layer = layer()
.json()
.with_writer(non_blocking_appender.with_max_level(level))
.with_thread_names(true)
.with_line_number(true);
layers = Some(match layers {
Some(layers) => layers.and_then(file_layer).boxed(),
None => file_layer.boxed(),
});
}
}
registry.with(layers.expect("log layers shoud be ok")).init();
tracing::debug!("hello debug");
tracing::info!("hello info");
tracing::warn!("hello warn");
tracing::error!("hello error");
tracing::info!(a_bool = true, answer = 42, message = "hello tracing");
}
you are dropping the worker guard too early, the worker thread wasn't flushing the file when the program exit. store the guard in a bigger scope. e.g. use an Option or Vec, depending how many potential worker guards you might have at runtime.
let mut guards = vec![];
for cfg in cfgs {
//...
let (non_blocking_appender, guard) = non_blocking(file_appender);
guards.push(guard);
//...
}