-
Notifications
You must be signed in to change notification settings - Fork 715
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unable to create a Registry
with a dynamic number of Layer
s
#1708
Comments
There was previously a discussion on discord about something like this, where I suggested that the user implement a type like pub struct DynLayerList<S>(Vec<Box<dyn Layer<S> + Send + Sync + 'static>>);
impl<S> Layer<S> for DynLayerList<S> {
fn on_event(&self, event: Event<'_>, cx: Context<'_, S>) {
for layer in self.0 {
layer.on_event(event, cx);
}
}
// and then do this for every other layer callback...
} and, I believe that solved his problem. So, a solution like that should work for you right now. If we added it in |
Yeah, just came to basically the same solution. Took a while to get the Thanks for the quick reply! I'll see if I can whip up a PR once I got it working. |
This solution/workaround seems to break per-layer filtering. Is that expected? Or have I missed something? |
In what way does it break per-layer filtering --- do you mean that when a per-layer filter is added to a Are you able to share your implementation? Theoretically, I think should be possible to implement a dynamic list of |
I wasn't sure if all of the methods needed to be implemented. I had the same behaviour before and after overriding the default implementations (except for maybe the max_level_hint()) pub struct DynLayerList<S>(Vec<Box<dyn Layer<S> + Send + Sync + 'static>>);
impl<S> Layer<S> for DynLayerList<S>
where
S: Subscriber,
{
fn on_layer(&mut self, subscriber: &mut S) {
for layer in &mut self.0 {
layer.on_layer(subscriber);
}
}
fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
// Return highest level of interest.
let mut interest = Interest::never();
for layer in &self.0 {
let new_interest = layer.register_callsite(metadata);
if (interest.is_sometimes() && new_interest.is_always())
|| (interest.is_never() && !new_interest.is_never())
{
interest = new_interest;
}
}
interest
}
fn enabled(
&self,
metadata: &Metadata<'_>,
ctx: tracing_subscriber::layer::Context<'_, S>,
) -> bool {
for layer in &self.0 {
if layer.enabled(metadata, ctx.clone()) {
return true;
}
}
false
}
fn on_new_span(
&self,
attrs: &Attributes<'_>,
id: &Id,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
for layer in &self.0 {
layer.on_new_span(attrs, id, ctx.clone());
}
}
fn max_level_hint(&self) -> Option<LevelFilter> {
for layer in &self.0 {
if let Some(x) = layer.max_level_hint() {
return Some(x);
}
}
None
}
fn on_record(
&self,
span: &Id,
values: &Record<'_>,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
for layer in &self.0 {
layer.on_record(span, values, ctx.clone())
}
}
fn on_follows_from(
&self,
span: &Id,
follows: &Id,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
for layer in &self.0 {
layer.on_follows_from(span, follows, ctx.clone());
}
}
fn on_event(&self, event: &Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
for layer in &self.0 {
layer.on_event(event, ctx.clone());
}
}
fn on_enter(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
for layer in &self.0 {
layer.on_enter(id, ctx.clone());
}
}
fn on_exit(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
for layer in &self.0 {
layer.on_exit(id, ctx.clone());
}
}
fn on_close(&self, id: Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
for layer in &self.0 {
layer.on_close(id.clone(), ctx.clone());
}
}
fn on_id_change(&self, old: &Id, new: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
for layer in &self.0 {
layer.on_id_change(old, new, ctx.clone());
}
}
} Then adding to the list like so: let mut dyn_layers = DynLayerList(vec![]);
let target_filter = filter::Targets::new().with_targets(targets); // targets is HashMap<String, LevelFilter>
let l = tracing_subscriber::fmt::layer().with_filter(target_filter);
dyn_layers.0.push(Box::new(l));
let registry = Registry::default().with(dyn_layers);
set_global_default(registry)?; A first-party implementation would be awesome. I'm finding it really difficult to add anything conditionally. |
@steveocr I think the For example, suppose we have a list of
your code will return the Rather than returning the first hint any layer gives us, we need to always iterate over the entire list, and return the least restrictive hint that any layer returns. The layers with more restrictive per-layer filters will still be able to filter out things they don't care about, but the layers with less restrictive filters will get the opportunity to record everything they're interested in. I believe the correct behavior is:
I still think we should probably add a version of this to |
Aha, that makes sense. Thank you! After some more experimenting it turned out the main issue was with fn enabled(
&self,
metadata: &Metadata<'_>,
ctx: tracing_subscriber::layer::Context<'_, S>,
) -> bool {
let mut enabled = false;
for layer in &self.0 {
if layer.enabled(metadata, ctx.clone()) {
enabled = true;
}
}
enabled
} I also changed fn max_level_hint(&self) -> Option<LevelFilter> {
let mut max_level = None;
for layer in &self.0 {
let x = layer.max_level_hint()?;
match max_level {
Some(y) => {
if x > y {
max_level = Some(x);
}
}
None => max_level = Some(x),
}
}
max_level
} I think having this functionality built-in would be awesome. |
@steveocr ah, yeah, that looks right, I missed that the FWIW, I think both of these can be written a little bit simpler: fn enabled(
&self,
metadata: &Metadata<'_>,
ctx: tracing_subscriber::layer::Context<'_, S>,
) -> bool {
for layer in &self.0 {
if layer.enabled(metadata, ctx.clone()) {
return true;
}
}
false
}
fn max_level_hint(&self) -> Option<LevelFilter> {
let mut max_level = LevelFilter::ERROR;
for layer in &self.0 {
let x = layer.max_level_hint()?;
max_level = std::cmp::max(x, max_level);
}
Some(max_level)
} |
@hawkw That works great! Thanks! |
Depends on #2028 ## Motivation In many cases, it is desirable to have a variable number of subscribers at runtime (such as when reading a logging configuration from a config file). The current approach, where types implementing `Subscribe` are composed at the type level into `Layered` subscribers, doesn't work well when the number of subscribers varies at runtime. ## Solution To solve this, this branch adds a `Subscribe` implementation for `Vec<S> where S: Subscribe`. This allows variable-length lists of subscribers to be added to a collector. Although the impl for `Vec<S>` requires all the subscribers to be the same type, it can also be used in conjunction with `Box<dyn Subscribe<C> + ...>` trait objects to implement a variable-length list of subscribers of multiple types. I also wrote a bunch of docs examples. ## Notes Alternatively, we could have a separate type defined in `tracing-subscriber` for a variable-length list of type-erased subscribers. This would have one primary usability benefit, which is that we could have a `push` operation that takes an `impl Subscribe` and boxes it automatically. However, I thought the approach used here is nicer, since it's based on composing together existing primitives such as `Vec` and `Box`, rather than adding a whole new API. Additionally, it allows avoiding the additional `Box`ing in the case where the list consists of subscribers that are all the same type. Closes #1708 Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Depends on #2028 ## Motivation In many cases, it is desirable to have a variable number of `Layer`s at runtime (such as when reading a logging configuration from a config file). The current approach, where types implementing `Layer` are composed at the type level into `Layered` layers, doesn't work well when the number of layers varies at runtime. ## Solution To solve this, this branch adds a `Layer` implementation for `Vec<L> where L: Layer`. This allows variable-length lists of layers to be added to a subscriber. Although the impl for `Vec<L>` requires all the layers to be the same type, it can also be used in onjunction with `Box<dyn Layer<S> + ...>` trait objects to implement a variable-length list of layers of multiple types. I also wrote a bunch of docs examples. ## Notes Alternatively, we could have a separate type defined in `tracing-subscriber` for a variable-length list of type-erased `Layer`s. This would have one primary usability benefit, which is that we could have a `push` operation that takes an `impl Layer` and boxes it automatically. However, I thought the approach used here is nicer, since it's based on composing together existing primitives such as `Vec` and `Box`, rather than adding a whole new API. Additionally, it allows avoiding the additional `Box`ing in the case where the list consists of layers that are all the same type. Closes #1708 Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Depends on #2028 ## Motivation In many cases, it is desirable to have a variable number of `Layer`s at runtime (such as when reading a logging configuration from a config file). The current approach, where types implementing `Layer` are composed at the type level into `Layered` layers, doesn't work well when the number of layers varies at runtime. ## Solution To solve this, this branch adds a `Layer` implementation for `Vec<L> where L: Layer`. This allows variable-length lists of layers to be added to a subscriber. Although the impl for `Vec<L>` requires all the layers to be the same type, it can also be used in onjunction with `Box<dyn Layer<S> + ...>` trait objects to implement a variable-length list of layers of multiple types. I also wrote a bunch of docs examples. ## Notes Alternatively, we could have a separate type defined in `tracing-subscriber` for a variable-length list of type-erased `Layer`s. This would have one primary usability benefit, which is that we could have a `push` operation that takes an `impl Layer` and boxes it automatically. However, I thought the approach used here is nicer, since it's based on composing together existing primitives such as `Vec` and `Box`, rather than adding a whole new API. Additionally, it allows avoiding the additional `Box`ing in the case where the list consists of layers that are all the same type. Closes #1708 Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Depends on #2028 ## Motivation In many cases, it is desirable to have a variable number of `Layer`s at runtime (such as when reading a logging configuration from a config file). The current approach, where types implementing `Layer` are composed at the type level into `Layered` layers, doesn't work well when the number of layers varies at runtime. ## Solution To solve this, this branch adds a `Layer` implementation for `Vec<L> where L: Layer`. This allows variable-length lists of layers to be added to a subscriber. Although the impl for `Vec<L>` requires all the layers to be the same type, it can also be used in onjunction with `Box<dyn Layer<S> + ...>` trait objects to implement a variable-length list of layers of multiple types. I also wrote a bunch of docs examples. ## Notes Alternatively, we could have a separate type defined in `tracing-subscriber` for a variable-length list of type-erased `Layer`s. This would have one primary usability benefit, which is that we could have a `push` operation that takes an `impl Layer` and boxes it automatically. However, I thought the approach used here is nicer, since it's based on composing together existing primitives such as `Vec` and `Box`, rather than adding a whole new API. Additionally, it allows avoiding the additional `Box`ing in the case where the list consists of layers that are all the same type. Closes #1708 Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Depends on #2028 ## Motivation In many cases, it is desirable to have a variable number of `Layer`s at runtime (such as when reading a logging configuration from a config file). The current approach, where types implementing `Layer` are composed at the type level into `Layered` layers, doesn't work well when the number of layers varies at runtime. ## Solution To solve this, this branch adds a `Layer` implementation for `Vec<L> where L: Layer`. This allows variable-length lists of layers to be added to a subscriber. Although the impl for `Vec<L>` requires all the layers to be the same type, it can also be used in onjunction with `Box<dyn Layer<S> + ...>` trait objects to implement a variable-length list of layers of multiple types. I also wrote a bunch of docs examples. ## Notes Alternatively, we could have a separate type defined in `tracing-subscriber` for a variable-length list of type-erased `Layer`s. This would have one primary usability benefit, which is that we could have a `push` operation that takes an `impl Layer` and boxes it automatically. However, I thought the approach used here is nicer, since it's based on composing together existing primitives such as `Vec` and `Box`, rather than adding a whole new API. Additionally, it allows avoiding the additional `Box`ing in the case where the list consists of layers that are all the same type. Closes #1708 Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Depends on tokio-rs#2028 ## Motivation In many cases, it is desirable to have a variable number of `Layer`s at runtime (such as when reading a logging configuration from a config file). The current approach, where types implementing `Layer` are composed at the type level into `Layered` layers, doesn't work well when the number of layers varies at runtime. ## Solution To solve this, this branch adds a `Layer` implementation for `Vec<L> where L: Layer`. This allows variable-length lists of layers to be added to a subscriber. Although the impl for `Vec<L>` requires all the layers to be the same type, it can also be used in onjunction with `Box<dyn Layer<S> + ...>` trait objects to implement a variable-length list of layers of multiple types. I also wrote a bunch of docs examples. ## Notes Alternatively, we could have a separate type defined in `tracing-subscriber` for a variable-length list of type-erased `Layer`s. This would have one primary usability benefit, which is that we could have a `push` operation that takes an `impl Layer` and boxes it automatically. However, I thought the approach used here is nicer, since it's based on composing together existing primitives such as `Vec` and `Box`, rather than adding a whole new API. Additionally, it allows avoiding the additional `Box`ing in the case where the list consists of layers that are all the same type. Closes tokio-rs#1708 Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Feature Request
Crates
Probably at least
tracing-subscriber
, maybe also others (tracing-core
?).Motivation
I would like to use a dynamic number of layers with a
Registry
in order to configure (at startup-time) the set of output files and of what goes where.So something like this:
Proposal
No real idea, the issue seems to be that every
SubscriberExt::with()
call returns a different type, and so this can't be made dynamic.While not a solution, this issue seems to run into similar problems: #575
Edit: Maybe something like
impl Layer for Vec<Box<dyn Layer>>
could work? Then such aVec
could be added to the registry withSubscriberExt::with()
.Alternatives
I tried doing this with a form of type-erased objects, but this doesn't work:
Fails because
SubscriberInitExt
is not object-safe. I also tried withInto<Dispatch>
instead ofSubscriberInitExt
(which can be converted automatically), butInto<Dispatch>
is also not object-safe, probably becausecore::convert::Into<T>
requiresSelf: Sized
(note:std::convert::Into
does not show this bound, but it actually seems to be the same type ascore::convert::Into
, and thus seems to require it as well).Is this possible somehow/am I missing something?
The text was updated successfully, but these errors were encountered: