Skip to content

Commit 51a9457

Browse files
committed
logging: make log levels/format configurable
This allows users to prevent the root logger from swallowing logs, for example logs emitted by plugins and dependencies (such as the MusicBrainz API client).
1 parent a2963fa commit 51a9457

File tree

2 files changed

+84
-8
lines changed

2 files changed

+84
-8
lines changed

beets/logging.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
WARNING,
3030
FileHandler,
3131
Filter,
32+
Formatter,
3233
Handler,
3334
Logger,
3435
NullHandler,
3536
StreamHandler,
37+
_levelToName,
3638
)
3739

3840
__all__ = [
@@ -42,14 +44,28 @@
4244
"WARNING",
4345
"FileHandler",
4446
"Filter",
47+
"Formatter",
4548
"Handler",
4649
"Logger",
4750
"NullHandler",
4851
"StreamHandler",
4952
"getLogger",
53+
"getLevelNamesMapping",
5054
]
5155

5256

57+
def getLevelNamesMapping() -> dict[str, int]:
58+
"""Returns a mapping from level names to their corresponding logging
59+
levels. For example, the string “CRITICAL” maps to CRITICAL. The returned
60+
mapping is copied from an internal mapping on each call to this function.
61+
62+
Polyfill for `logging.getLevelNamesMapping`, which was only added in Python
63+
3.11.
64+
"""
65+
66+
return {name: level for (level, name) in _levelToName.items()}
67+
68+
5369
def logsafe(val):
5470
"""Coerce `bytes` to `str` to avoid crashes solely due to logging.
5571

beets/ui/__init__.py

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import textwrap
3030
import traceback
3131
from difflib import SequenceMatcher
32-
from typing import TYPE_CHECKING, Any, Callable
32+
from typing import TYPE_CHECKING, Any, Callable, OrderedDict, cast
3333

3434
import confuse
3535

@@ -1637,13 +1637,7 @@ def _configure(options):
16371637
overlay_path = None
16381638
config.set_args(options)
16391639

1640-
beet_logger = logging.getLogger("beets")
1641-
1642-
# Configure the logger.
1643-
if config["verbose"].get(int):
1644-
beet_logger.set_global_level(logging.DEBUG)
1645-
else:
1646-
beet_logger.set_global_level(logging.INFO)
1640+
_configure_logging()
16471641

16481642
if overlay_path:
16491643
log.debug(
@@ -1663,6 +1657,72 @@ def _configure(options):
16631657
return config
16641658

16651659

1660+
def _configure_logging():
1661+
"""Configure logging levels and handlers."""
1662+
1663+
if config["verbose"].get(int):
1664+
level = logging.DEBUG
1665+
else:
1666+
level = logging.INFO
1667+
1668+
root_logger = logging.getLogger()
1669+
beet_logger = logging.getLogger("beets")
1670+
1671+
root_logger.setLevel(level)
1672+
beet_logger.set_global_level(level)
1673+
1674+
log_format = config["logging"]["format"].get(
1675+
confuse.String(default="{message}")
1676+
)
1677+
date_format = config["logging"]["date_format"].get(
1678+
confuse.String(default="%Y-%m-%d %H:%M:%S")
1679+
)
1680+
1681+
# Remove the `StreamHandler` we added earlier so we can make a new one with
1682+
# the user's format.
1683+
for logger in [log, beet_logger, root_logger]:
1684+
stream_handlers = [
1685+
handler
1686+
for handler in logger.handlers
1687+
if isinstance(handler, logging.StreamHandler)
1688+
]
1689+
for handler in stream_handlers:
1690+
logger.removeHandler(handler)
1691+
1692+
handler = logging.StreamHandler()
1693+
handler.setFormatter(
1694+
logging.Formatter(
1695+
fmt=cast(str, log_format),
1696+
datefmt=cast(str, date_format),
1697+
style="{",
1698+
validate=True,
1699+
)
1700+
)
1701+
logger.addHandler(handler)
1702+
1703+
level_names_mapping = logging.getLevelNamesMapping()
1704+
log_levels = config["logging"]["levels"].get(
1705+
confuse.MappingValues(
1706+
confuse.OneOf(
1707+
allowed=[
1708+
confuse.Integer(),
1709+
confuse.OneOf(allowed=level_names_mapping.keys()),
1710+
]
1711+
)
1712+
)
1713+
)
1714+
1715+
if log_levels:
1716+
for name, level in cast(OrderedDict, log_levels).items():
1717+
name = None if name == "root" else name
1718+
logger = logging.getLogger(name)
1719+
if isinstance(level, str):
1720+
level = level_names_mapping[level]
1721+
logger.setLevel(level)
1722+
if hasattr(logger, "set_global_level"):
1723+
logger.set_global_level(level)
1724+
1725+
16661726
def _ensure_db_directory_exists(path):
16671727
if path == b":memory:": # in memory db
16681728
return

0 commit comments

Comments
 (0)