Skip to content

Commit e4c02f4

Browse files
authored
Add a function to get a public logger for a module (#23)
This function will return a logger for the public module containing the given module name. This is useful to make sure that logger name only reference public modules, which are the ones that the user imports and interacts with. Implements the function suggested in frequenz-floss/frequenz-sdk-python#1009.
2 parents 1e40791 + c2d1e7c commit e4c02f4

File tree

4 files changed

+76
-0
lines changed

4 files changed

+76
-0
lines changed

.github/keylabeler.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ labelMappings:
1515
"part:asyncio": "part:asyncio"
1616
"part:datetime": "part:datetime"
1717
"part:docs": "part:docs"
18+
"part:logging": "part:logging"
1819
"part:math": "part:math"
1920
"part:tests": "part:tests"
2021
"part:tooling": "part:tooling"

.github/labeler.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@
5555
- "examples/**"
5656
- LICENSE
5757

58+
"part:logging":
59+
- changed-files:
60+
- any-glob-to-any-file:
61+
- "src/frequenz/core/logging.py"
62+
- "src/frequenz/core/logging/**"
63+
- "tests/test_logging.py"
64+
- "tests/logging/**"
65+
5866
"part:math":
5967
- changed-files:
6068
- any-glob-to-any-file:

src/frequenz/core/logging.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Logging tools."""
5+
6+
import logging
7+
8+
9+
def get_public_logger(module_name: str) -> logging.Logger:
10+
"""Get a logger for the public module containing the given module name.
11+
12+
* Modules are considered private if they start with `_`.
13+
* All modules inside a private module are also considered private, even if they
14+
don't start with `_`.
15+
* If there is no leading public part, the root logger is returned.
16+
17+
Example:
18+
Here are a few examples of how this function will resolve module names:
19+
20+
* `some.pub` -> `some.pub`
21+
* `some.pub._some._priv` -> `some.pub`
22+
* `some.pub._some._priv.public` -> `some.pub`
23+
* `some.pub._some._priv.public._private` -> `some.pub`
24+
* `_priv` -> `root`
25+
26+
Args:
27+
module_name: The fully qualified name of the module to get the logger for
28+
(normally the `__name__` built-in variable).
29+
30+
Returns:
31+
The logger for the public module containing the given module name.
32+
"""
33+
public_parts: list[str] = []
34+
for part in module_name.split("."):
35+
if part.startswith("_"):
36+
break
37+
public_parts.append(part)
38+
return logging.getLogger(".".join(public_parts))

tests/test_logging.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# License: MIT
2+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the log module."""
5+
6+
7+
import pytest
8+
9+
from frequenz.core.logging import get_public_logger
10+
11+
12+
@pytest.mark.parametrize(
13+
"module_name, expected_logger_name",
14+
[
15+
("some.pub", "some.pub"),
16+
("some.pub._some._priv", "some.pub"),
17+
("some.pub._some._priv.public", "some.pub"),
18+
("some.pub._some._priv.public._private", "some.pub"),
19+
("some._priv.pub", "some"),
20+
("_priv.some.pub", "root"),
21+
("some", "some"),
22+
("some._priv", "some"),
23+
("_priv", "root"),
24+
],
25+
)
26+
def test_get_public_logger(module_name: str, expected_logger_name: str) -> None:
27+
"""Test that the logger name is as expected."""
28+
logger = get_public_logger(module_name)
29+
assert logger.name == expected_logger_name

0 commit comments

Comments
 (0)