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

Filter log levels by target #10729

Merged
merged 7 commits into from Sep 4, 2020
Merged

Conversation

gshuflin
Copy link
Contributor

@gshuflin gshuflin commented Sep 3, 2020

Problem

Pants's logging, particularly on the DEBUG and TRACE levels, is too verbose to make effective use of. We would like to be able to selectively enable more verbose logging for specific areas of the pants codebase where we think a problem we're trying to debug might lie, while leaving others at the default logging level.

Solution

The Rust and Python logging frameworks that Pants uses already have the concept of a target, that is, a name associated with a log object that comes from the area of the codebase where that log is emitted. For Python logs, that target name is a fully-qualified Python module (e.g. pants.pantsd.service.pants_service) and for Rust logs it is the name of the Rust module (e.g. workunit_store).

This commit adds a new global option --log-levels-by-target that expects a dictionary mapping strings representing these target names to the string representation of a Pants log level. By this mechanism, a user can specify that logs originating in a given part of the pants codebase will be subject to less filtering than other logs. Log targets not explicitly specified in this option are filtered according to the global log level specified with --level.

This commit also adds another new global option --show-log-target that simply adds the target name to log messages without doing any filtering, to make it easier for users casually inspecting logs to see what log targets might be relevant for further debugging.

Copy link
Contributor

@tdyas tdyas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

set_max_level(level);
// TODO this should be whatever the most verbose log level specified in log_domain_levels -
// but I'm not sure if it's actually much of a gain over just setting this to Trace.
set_max_level(LevelFilter::Trace);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we would be formatting the strings for log messages only to discard them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tdyas with this change we would execute format strings in Rust log! macros (and debug!, etc.), but we wouldn't generate the final format string in PantsLogger::log since that only gets executed if PantsLogger::enabled is true. If the performance hit from doing too much trace-level string formatting is too bad, we could probably hook into whatever system the log macros are using to know whether they need to print, and make that aware of this option too.

Copy link
Contributor

@Eric-Arellano Eric-Arellano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat!

@@ -112,13 +146,26 @@ impl PantsLogger {

impl Log for PantsLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= max_level()
let global_level: LevelFilter = { *self.global_level.lock().borrow() };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is calling *foo.borrow() the same thing as *&foo, which I would expect to be the same thing as foo but I think isn't?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

borrow is a method defined on RefCell (https://doc.rust-lang.org/std/cell/struct.RefCell.html#method.borrow). However, I don't think I actually need Mutex<RefCell<T>>, just a Mutex should be fine here, so I'm removing this call.

let global_level: LevelFilter = { *self.global_level.lock().borrow() };
let enabled_globally = metadata.level() <= global_level;
let log_level_filters_lock = self.log_level_filters.lock();
let log_level_filters = log_level_filters_lock.borrow();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing as &log_level_filters_lock? (I'm trying to understand .borrow())

type=bool,
default=False,
advanced=True,
help="Display the target where a log message originates in that log message's output.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add: "This can be helpful when paired with the option --log-levels-by-target"

Comment on lines 187 to 189
help="Set a more specific logging level for one or more logging targets. "
"The logging levels are one of: "
'"error", "warn", "info", "debug", "trace".',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to give an example. Maybe something like:

Set a more specific logging level for one or more logging targets. You can find the target names by enabling --log-levels-by-target. The logging levels are one of "error", "warn", "info", "debug", "trace". All unspecified targets use the global level set by -l/--level. For example, you can set {"workunit_store": "info", "pants.engine.rules": "warn"}

Comment on lines 140 to 142
raise ValueError("keys for log_domain_levels must be strings")
if not isinstance(value, str):
raise ValueError("values for log_domain_levels must be strings")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to include the bad value.

"Keys for log_domain_levels must be strings, but was given the key 1232 with type 'int'."

log_show_rust_3rdparty: bool,
use_color: bool,
show_target: bool,
log_levels_by_target: Dict[str, LogLevel],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the type hint for log_levels_by_target everywhere can be Mapping, given that you convert it into a dict on the next line.

Not a big deal, but can be useful given how frequently we use FrozenDict.

Comment on lines -41 to +50
self.native.write_log(
msg=self.format(record), level=record.levelno, target=f"{record.name}:pid={os.getpid()}"
)
self.native.write_log(msg=self.format(record), level=record.levelno, target=record.name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to getting rid of the PID.

Comment on lines 37 to 40
sources = {
"src/python/project/__init__.py": "",
"src/python/project/lib.py": dedent(
"""\
def add(x: int, y: int) -> int:
return x + y
"""
),
"src/python/project/BUILD": "python_library()",
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than copying and pasting, can you please either use a @pytest.fixture or make this value be a module-level constant?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I don't think there's any need for this source file, if all you're running is list. It's fine to have no tmpdir afaict.

@coveralls
Copy link

coveralls commented Sep 4, 2020

Coverage Status

Coverage remained the same at 0.0% when pulling 69b56db on gshuflin:filter_logs into 91921db on pantsbuild:master.

Also remove PID in log target, explicitly log pantsd PID one time at
startup

[ci skip-build-wheels]
[ci skip-build-wheels]
# Building wheels and fs_util will be skipped. Delete if not intended.
[ci skip-build-wheels]
# Building wheels and fs_util will be skipped. Delete if not intended.
[ci skip-build-wheels]
# Building wheels and fs_util will be skipped. Delete if not intended.
[ci skip-build-wheels]
# Building wheels and fs_util will be skipped. Delete if not intended.
[ci skip-build-wheels]
@gshuflin gshuflin merged commit a403445 into pantsbuild:master Sep 4, 2020
@stuhood
Copy link
Sponsor Member

stuhood commented Sep 14, 2020

(Large?) nit: "target" already has a pretty specific meaning in pants: https://www.pantsbuild.org/docs/target-api

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

Successfully merging this pull request may close these issues.

None yet

5 participants