pw_cli: Allow setting log levels for all loggers
- Make it easier to allow debug logs through the root logger but only
for selected modules. The set_all_loggers_minimum_level(level)
function increases all logger's log level to the provided minimum.
Then, individual loggers can be dropped to debug or a different level.
- Remove the unsued and ambiguous pw_cli.log.set_level function.
Change-Id: I85b4766737aa7a54ff81e8e8229ac0158d84ff91
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/37562
Pigweed-Auto-Submit: Wyatt Hepler <hepler@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_cli/BUILD.gn b/pw_cli/BUILD.gn
index dd021e8..79f575b 100644
--- a/pw_cli/BUILD.gn
+++ b/pw_cli/BUILD.gn
@@ -18,4 +18,5 @@
pw_doc_group("docs") {
sources = [ "docs.rst" ]
+ other_deps = [ "py" ]
}
diff --git a/pw_cli/docs.rst b/pw_cli/docs.rst
index 5d03f05..40489a2 100644
--- a/pw_cli/docs.rst
+++ b/pw_cli/docs.rst
@@ -265,3 +265,13 @@
- Supporting renaming the ``pw`` command to something project specific, like
``foo`` in this case.
- Re-coloring the log headers from the ``pw`` tool.
+
+pw_cli Python package
+=====================
+The ``pw_cli`` Pigweed module includes the ``pw_cli`` Python package, which
+provides utilities for creating command line tools with Pigweed.
+
+pw_cli log
+----------
+.. automodule:: pw_cli.log
+ :members:
diff --git a/pw_cli/py/pw_cli/__main__.py b/pw_cli/py/pw_cli/__main__.py
index 096f341..799cbc4 100644
--- a/pw_cli/py/pw_cli/__main__.py
+++ b/pw_cli/py/pw_cli/__main__.py
@@ -29,8 +29,7 @@
args = arguments.parse_args()
- pw_cli.log.install()
- pw_cli.log.set_level(args.loglevel)
+ pw_cli.log.install(level=args.loglevel)
# Start with the most critical part of the Pigweed command line tool.
if not args.no_banner:
diff --git a/pw_cli/py/pw_cli/log.py b/pw_cli/py/pw_cli/log.py
index d84b657..df5ebdc 100644
--- a/pw_cli/py/pw_cli/log.py
+++ b/pw_cli/py/pw_cli/log.py
@@ -11,11 +11,11 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
-"""Configure the system logger for the default pw command log format."""
+"""Tools for configuring Python logging."""
import logging
from pathlib import Path
-from typing import NamedTuple, Union
+from typing import NamedTuple, Union, Iterator
import pw_cli.color
import pw_cli.env
@@ -25,7 +25,7 @@
LOGLEVEL_STDOUT = 21
-class LogLevel(NamedTuple):
+class _LogLevel(NamedTuple):
level: int
color: str
ascii: str
@@ -35,12 +35,12 @@
# Shorten all the log levels to 3 characters for column-aligned logs.
# Color the logs using ANSI codes.
_LOG_LEVELS = (
- LogLevel(logging.CRITICAL, 'bold_red', 'CRT', '☠️ '),
- LogLevel(logging.ERROR, 'red', 'ERR', '❌'),
- LogLevel(logging.WARNING, 'yellow', 'WRN', '⚠️ '),
- LogLevel(logging.INFO, 'magenta', 'INF', 'ℹ️ '),
- LogLevel(LOGLEVEL_STDOUT, 'cyan', 'OUT', '💬'),
- LogLevel(logging.DEBUG, 'blue', 'DBG', '👾'),
+ _LogLevel(logging.CRITICAL, 'bold_red', 'CRT', '☠️ '),
+ _LogLevel(logging.ERROR, 'red', 'ERR', '❌'),
+ _LogLevel(logging.WARNING, 'yellow', 'WRN', '⚠️ '),
+ _LogLevel(logging.INFO, 'magenta', 'INF', 'ℹ️ '),
+ _LogLevel(LOGLEVEL_STDOUT, 'cyan', 'OUT', '💬'),
+ _LogLevel(logging.DEBUG, 'blue', 'DBG', '👾'),
) # yapf: disable
_LOG = logging.getLogger(__name__)
@@ -48,7 +48,7 @@
def main() -> None:
- """Show how logs look at various levels."""
+ """Shows how logs look at various levels."""
# Force the log level to make sure all logs are shown.
_LOG.setLevel(logging.DEBUG)
@@ -62,11 +62,18 @@
_LOG.debug('Adding 1 to i')
+def _setup_handler(handler: logging.Handler, formatter: logging.Formatter,
+ level: int) -> None:
+ handler.setLevel(level)
+ handler.setFormatter(formatter)
+ logging.getLogger().addHandler(handler)
+
+
def install(level: int = logging.INFO,
use_color: bool = None,
hide_timestamp: bool = False,
log_file: Union[str, Path] = None) -> None:
- """Configure the system logger for the default pw command log format."""
+ """Configures the system logger for the default pw command log format."""
colors = pw_cli.color.colors(use_color)
@@ -81,17 +88,20 @@
# colored text.
timestamp_fmt = colors.black_on_white('%(asctime)s') + ' '
- # Set log level on root logger to debug, otherwise any higher levels
- # elsewhere are ignored.
- root = logging.getLogger()
- root.setLevel(logging.DEBUG)
+ formatter = logging.Formatter(timestamp_fmt + '%(levelname)s %(message)s',
+ '%Y%m%d %H:%M:%S')
- handler = logging.FileHandler(log_file) if log_file else _STDERR_HANDLER
- handler.setLevel(level)
- handler.setFormatter(
- logging.Formatter(timestamp_fmt + '%(levelname)s %(message)s',
- '%Y%m%d %H:%M:%S'))
- root.addHandler(handler)
+ # Set the log level on the root logger to 1, so logs that all logs
+ # propagated from child loggers are handled.
+ logging.getLogger().setLevel(1)
+
+ # Always set up the stderr handler, even if it isn't used.
+ _setup_handler(_STDERR_HANDLER, formatter, level)
+
+ if log_file:
+ _setup_handler(logging.FileHandler(log_file), formatter, level)
+ # Since we're using a file, filter logs out of the stderr handler.
+ _STDERR_HANDLER.setLevel(logging.CRITICAL + 1)
if env.PW_EMOJI:
name_attr = 'emoji'
@@ -105,9 +115,19 @@
logging.addLevelName(log_level.level, colorize(log_level)(name))
-def set_level(log_level: int):
- """Sets the log level for logs to stderr."""
- _STDERR_HANDLER.setLevel(log_level)
+def all_loggers() -> Iterator[logging.Logger]:
+ """Iterates over all loggers known to Python logging."""
+ manager = logging.getLogger().manager # type: ignore[attr-defined]
+
+ for logger_name in manager.loggerDict: # pylint: disable=no-member
+ yield logging.getLogger(logger_name)
+
+
+def set_all_loggers_minimum_level(level: int) -> None:
+ """Increases the log level to the specified value for all known loggers."""
+ for logger in all_loggers():
+ if logger.isEnabledFor(level - 1):
+ logger.setLevel(level)
if __name__ == '__main__':