Skip to content
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

Targets::with_targets ignores default #1790

Closed
recmo opened this issue Dec 20, 2021 · 3 comments
Closed

Targets::with_targets ignores default #1790

recmo opened this issue Dec 20, 2021 · 3 comments

Comments

@recmo
Copy link

recmo commented Dec 20, 2021

Bug Report

Version

`cargo tree | grep tracing`
│   │   ├── tracing-core v0.1.21
│   │   └── tracing-error v0.1.2
│   │       ├── tracing v0.1.29
│   │       │   ├── tracing-attributes v0.1.18 (proc-macro)
│   │       │   └── tracing-core v0.1.21 (*)
│   │       └── tracing-subscriber v0.2.25
│   │           └── tracing-core v0.1.21 (*)
│   └── tracing-error v0.1.2 (*)
│   │   │   │   │   └── tracing v0.1.29 (*)
│   │   │   │   └── tracing v0.1.29 (*)
│   │   │   │   ├── tracing v0.1.29 (*)
│   │   │   │   └── tracing v0.1.29 (*)
│   │   │   ├── tracing v0.1.29 (*)
│   │   │   └── tracing-futures v0.2.5
│   │   │       └── tracing v0.1.29 (*)
│   │   └── tracing-core v0.1.21 (*)
│   ├── tracing v0.1.29 (*)
│   ├── tracing-core v0.1.21 (*)
│   └── tracing-subscriber v0.3.3
│       ├── tracing v0.1.29 (*)
│       ├── tracing-core v0.1.21 (*)
│       ├── tracing-log v0.1.2
│       │   └── tracing-core v0.1.21 (*)
│       └── tracing-serde v0.1.2
│           └── tracing-core v0.1.21 (*)
│   │   │   │   │   │   ├── tracing v0.1.29
│   │   │   │   │   │   │   └── tracing-core v0.1.21
│   │   │   ├── tracing v0.1.29 (*)
│   │   │   ├── tracing-futures v0.2.5 (*)
│   │   ├── tracing v0.1.29 (*)
│   │   ├── tracing-futures v0.2.5 (*)
│       ├── tracing v0.1.29 (*)
│       ├── tracing v0.1.29 (*)
│       └── tracing-futures v0.2.5 (*)
├── tracing v0.1.29 (*)
├── tracing-futures v0.2.5 (*)
├── tracing-opentelemetry v0.16.0
│   ├── tracing v0.1.29 (*)
│   ├── tracing-core v0.1.21 (*)
│   ├── tracing-log v0.1.2 (*)
│   └── tracing-subscriber v0.3.3 (*)
├── tracing-subscriber v0.3.3 (*)
├── tracing-test v0.2.1
│   ├── tracing-core v0.1.21 (*)
│   ├── tracing-subscriber v0.3.3 (*)
│   └── tracing-test-macro v0.2.1 (proc-macro)

Platform

Darwin MacBook-Pro-16-inch-2021-2.local 21.2.0 Darwin Kernel Version 21.2.0: Sun Nov 28 20:28:41 PST 2021; root:xnu-8019.61.5~1/RELEASE_ARM64_T6000 arm64

Crates

tracing_subscriber

Description

Merging two Targets objects using first.with_targets(second) ignores the default value set in second.

Example

let first = Targets::new()
    .with_default(Level::INFO)
    .with_target("app", Level::DEBUG);
let second = Targets::new()
    .with_default(Level::DEBUG)
    .with_target("app", Level::TRACE);
let combined = first.with_targets(second);
dbg!(&combined);

results in

[src/cli/logging.rs:56] &combined = Targets(
    DirectiveSet {
        directives: [
            StaticDirective {
                target: Some(
                    "app",
                ),
                field_names: [],
                level: LevelFilter::TRACE,
            },
            StaticDirective {
                target: None,
                field_names: [],
                level: LevelFilter::INFO,
            },
        ],
        max_level: LevelFilter::TRACE,
    },
)

where the app directive is correctly overridden by second, but the original default directive is retained.

In fact, there appears to be no method to access the default level on a Targets, so I can not even write a workaround.

I'm using this pattern to combine the --verbose CLI argument (first) with a --log-filter argument (second). I want the log-filter to take precedence for all directives, including default.

@benesch
Copy link

benesch commented Dec 22, 2021

I just ran into this too! I'd be happy to prepare a PR if someone has an opinion on what the appropriate fix is.

@hawkw
Copy link
Member

hawkw commented Dec 22, 2021

What do you think the expected behavior for the default directive when merging Targets should be? Currently, it does not change the default because I thought that changing the default would be more surprising than not changing it. If most people would expect the default directive to also be changed, we could certainly change the behavior here, but I'm not sure whether that would actually be less surprising.

Another potential option is to always pick the more verbose default, rather than always overwriting the default from the first set of targets with the default from the second one.

Alternatively, we could add an accessor that returns the default level, so that the user can use that to explicitly implement whatever merging logic they want, and document in the with_targets method that this method doesn't effect the default directive. I think that might be the best approach, because it allows users of the API to choose how the default directives are merged.

How do others feel about that solution?

@benesch
Copy link

benesch commented Dec 22, 2021

FWIW, the behavior I expected is that the last target to specify a "thing" wins, whether that "thing" is a specific target or the default. Isn't that the way all the other merging works? (If not then I am misinformed.)

I think that might be the best approach, because it allows users of the API to choose how the default directives are merged.

This works for me, though!

@hawkw hawkw closed this as completed in 0081037 Jul 25, 2022
hawkw pushed a commit that referenced this issue Jul 29, 2022
## Motivation

This makes it possible to fully "override" some base `Targets` filter
with another (e.g. user-supplied) filter. Without some way to obtain the
default, only explicit targets can be overridden (via `IntoIter` and
friends). 

See also #1790 (comment)

## Solution

We can add a method to `Targets` that filters the underlying
`DirectiveSet` for the default directive. This works because
`DirectiveSet::add` will replace directives with the same
`target`/`field_names`, which is always `None`/`vec![]` for the
directive added by `with_default` (and in fact we are only concerned
with `target`, since no other `Targets` API allows adding directives
with a `None` target).

Ideally the method would be named `default`, corresponding to
`with_default`, however this conflicts with `Default::default` and so
would be a breaking change (and harm ergonomics). `default_level` seemed
a better name than `get_default`, since "getters" of this style are
generally considered unidiomatic<sup>[citation needed]</sup>.

Example usage:

```rust
let mut filter = Targets::new().with_target("some_module", LevelFilter::INFO);

// imagine this came from `RUST_LOG` or similar
let override: Targets = "trace".parse().unwrap();

// merge the override
if let Some(default) = override.default_level() {
    filter = filter.with_default(default);
}
for (target, level) in override.iter() {
    filter = filter.with_target(target, level);
}
```

Closes #1790
hawkw pushed a commit that referenced this issue Jul 29, 2022
## Motivation

This makes it possible to fully "override" some base `Targets` filter
with another (e.g. user-supplied) filter. Without some way to obtain the
default, only explicit targets can be overridden (via `IntoIter` and
friends). 

See also #1790 (comment)

## Solution

We can add a method to `Targets` that filters the underlying
`DirectiveSet` for the default directive. This works because
`DirectiveSet::add` will replace directives with the same
`target`/`field_names`, which is always `None`/`vec![]` for the
directive added by `with_default` (and in fact we are only concerned
with `target`, since no other `Targets` API allows adding directives
with a `None` target).

Ideally the method would be named `default`, corresponding to
`with_default`, however this conflicts with `Default::default` and so
would be a breaking change (and harm ergonomics). `default_level` seemed
a better name than `get_default`, since "getters" of this style are
generally considered unidiomatic<sup>[citation needed]</sup>.

Example usage:

```rust
let mut filter = Targets::new().with_target("some_module", LevelFilter::INFO);

// imagine this came from `RUST_LOG` or similar
let override: Targets = "trace".parse().unwrap();

// merge the override
if let Some(default) = override.default_level() {
    filter = filter.with_default(default);
}
for (target, level) in override.iter() {
    filter = filter.with_target(target, level);
}
```

Closes #1790
liuhaozhan added a commit to liuhaozhan/tracing that referenced this issue Nov 17, 2022
## Motivation

This makes it possible to fully "override" some base `Targets` filter
with another (e.g. user-supplied) filter. Without some way to obtain the
default, only explicit targets can be overridden (via `IntoIter` and
friends). 

See also tokio-rs/tracing#1790 (comment)

## Solution

We can add a method to `Targets` that filters the underlying
`DirectiveSet` for the default directive. This works because
`DirectiveSet::add` will replace directives with the same
`target`/`field_names`, which is always `None`/`vec![]` for the
directive added by `with_default` (and in fact we are only concerned
with `target`, since no other `Targets` API allows adding directives
with a `None` target).

Ideally the method would be named `default`, corresponding to
`with_default`, however this conflicts with `Default::default` and so
would be a breaking change (and harm ergonomics). `default_level` seemed
a better name than `get_default`, since "getters" of this style are
generally considered unidiomatic<sup>[citation needed]</sup>.

Example usage:

```rust
let mut filter = Targets::new().with_target("some_module", LevelFilter::INFO);

// imagine this came from `RUST_LOG` or similar
let override: Targets = "trace".parse().unwrap();

// merge the override
if let Some(default) = override.default_level() {
    filter = filter.with_default(default);
}
for (target, level) in override.iter() {
    filter = filter.with_target(target, level);
}
```

Closes #1790
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants