Skip to content

Commit bd98a59

Browse files
committed
Made logging integration entirely opt-in and configurable on the user-side
1 parent 18c3d9b commit bd98a59

File tree

4 files changed

+75
-56
lines changed

4 files changed

+75
-56
lines changed

src/cleo/application.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import logging
43
import os
54
import re
65
import sys
@@ -30,7 +29,6 @@
3029
from cleo.io.io import IO
3130
from cleo.io.outputs.output import Verbosity
3231
from cleo.io.outputs.stream_output import StreamOutput
33-
from cleo.logging.cleo_handler import CleoHandler
3432
from cleo.terminal import Terminal
3533
from cleo.ui.ui import UI
3634

@@ -311,7 +309,6 @@ def run(
311309
io = self.create_io(input, output, error_output)
312310

313311
self._configure_io(io)
314-
self._configure_logging(io)
315312

316313
try:
317314
exit_code = self._run(io)
@@ -533,17 +530,6 @@ def _configure_io(self, io: IO) -> None:
533530
if shell_verbosity == -1:
534531
io.interactive(False)
535532

536-
def _configure_logging(self, io: IO) -> None:
537-
"""
538-
Configures the built-in logging package to write it's output via Cleo's output class.
539-
"""
540-
handler = CleoHandler(io.output)
541-
handler.setLevel(handler.remap_verbosity(io.output.verbosity))
542-
543-
root = logging.getLogger()
544-
root.addHandler(handler)
545-
root.setLevel(handler.level)
546-
547533
@property
548534
def _default_definition(self) -> Definition:
549535
return Definition(

src/cleo/logging/cleo_handler.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import logging
44

5+
from logging import LogRecord
56
from typing import TYPE_CHECKING
67
from typing import ClassVar
78

@@ -12,6 +13,25 @@
1213
from cleo.io.outputs.output import Output
1314

1415

16+
class CleoFilter:
17+
def __init__(self, output: Output):
18+
self.output = output
19+
20+
@property
21+
def current_loglevel(self) -> int:
22+
verbosity_mapping: dict[Verbosity, int] = {
23+
Verbosity.QUIET: logging.CRITICAL, # Nothing gets emitted to the output anyway
24+
Verbosity.NORMAL: logging.WARNING,
25+
Verbosity.VERBOSE: logging.INFO,
26+
Verbosity.VERY_VERBOSE: logging.DEBUG,
27+
Verbosity.DEBUG: logging.DEBUG,
28+
}
29+
return verbosity_mapping[self.output.verbosity]
30+
31+
def filter(self, record: LogRecord) -> bool:
32+
return record.levelno >= self.current_loglevel
33+
34+
1535
class CleoHandler(logging.Handler):
1636
"""
1737
A handler class which writes logging records, appropriately formatted,
@@ -28,6 +48,7 @@ class CleoHandler(logging.Handler):
2848
def __init__(self, output: Output):
2949
super().__init__()
3050
self.output = output
51+
self.addFilter(CleoFilter(output))
3152

3253
def emit(self, record: logging.LogRecord) -> None:
3354
"""
@@ -40,20 +61,10 @@ def emit(self, record: logging.LogRecord) -> None:
4061
has an 'encoding' attribute, it is used to determine how to do the
4162
output to the stream.
4263
"""
64+
4365
try:
4466
msg = self.tags.get(record.levelname, "") + self.format(record) + "</>"
4567
self.output.write(msg, new_line=True)
4668

4769
except Exception:
4870
self.handleError(record)
49-
50-
@staticmethod
51-
def remap_verbosity(verbosity: Verbosity) -> int:
52-
verbosity_mapping: dict[Verbosity, int] = {
53-
Verbosity.QUIET: logging.CRITICAL, # Nothing gets emitted to the output anyway
54-
Verbosity.NORMAL: logging.WARNING,
55-
Verbosity.VERBOSE: logging.INFO,
56-
Verbosity.VERY_VERBOSE: logging.DEBUG,
57-
Verbosity.DEBUG: logging.DEBUG,
58-
}
59-
return verbosity_mapping[verbosity]

tests/fixtures/foo4_command.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
from cleo.commands.command import Command
88

99

10+
_logger = logging.getLogger(__file__)
11+
12+
1013
def log_stuff() -> None:
11-
logging.debug("This is an debug log record")
12-
logging.info("This is an info log record")
13-
logging.warning("This is an warning log record")
14-
logging.error("This is an error log record")
14+
_logger.debug("This is an debug log record")
15+
_logger.info("This is an info log record")
16+
_logger.warning("This is an warning log record")
17+
_logger.error("This is an error log record")
1518

1619

1720
class Foo4Command(Command):

tests/test_logging.py

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,70 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
3+
import logging
44

55
import pytest
66

77
from cleo.application import Application
8+
from cleo.logging.cleo_handler import CleoHandler
89
from cleo.testers.application_tester import ApplicationTester
910
from tests.fixtures.foo4_command import Foo4Command
1011

1112

12-
if TYPE_CHECKING:
13-
from cleo.commands.command import Command
14-
15-
16-
@pytest.mark.parametrize("cmd", (Foo4Command(),))
17-
def test_run_with_logging_integration_normal(cmd: Command) -> None:
13+
@pytest.fixture
14+
def app() -> Application:
1815
app = Application()
16+
cmd = Foo4Command()
1917
app.add(cmd)
18+
app._default_command = cmd.name
19+
return app
20+
21+
22+
@pytest.fixture
23+
def tester(app: Application) -> ApplicationTester:
24+
app.catch_exceptions(False)
25+
return ApplicationTester(app)
26+
27+
28+
@pytest.fixture
29+
def root_logger() -> logging.Logger:
30+
root = logging.getLogger()
31+
root.setLevel(logging.NOTSET)
32+
return root
33+
2034

21-
tester = ApplicationTester(app)
22-
status_code = tester.execute(f"{cmd.name}")
35+
def test_run_with_logging_integration_normal(
36+
tester: ApplicationTester, root_logger: logging.Logger
37+
) -> None:
38+
handler = CleoHandler(tester.io.output)
39+
root_logger.addHandler(handler)
40+
41+
status_code = tester.execute("")
2342

2443
expected = "This is an warning log record\n" "This is an error log record\n"
2544

2645
assert status_code == 0
2746
assert tester.io.fetch_output() == expected
2847

2948

30-
@pytest.mark.parametrize("cmd", (Foo4Command(),))
31-
def test_run_with_logging_integration_quiet(cmd: Command) -> None:
32-
app = Application()
33-
app.add(cmd)
49+
def test_run_with_logging_integration_quiet(
50+
tester: ApplicationTester, root_logger: logging.Logger
51+
) -> None:
52+
handler = CleoHandler(tester.io.output)
53+
root_logger.addHandler(handler)
3454

35-
tester = ApplicationTester(app)
36-
status_code = tester.execute(f"{cmd.name} -q")
55+
status_code = tester.execute("-q")
3756

3857
assert status_code == 0
3958
assert tester.io.fetch_output() == ""
4059

4160

42-
@pytest.mark.parametrize("cmd", (Foo4Command(),))
43-
def test_run_with_logging_integration_verbose(cmd: Command) -> None:
44-
app = Application()
45-
app.add(cmd)
61+
def test_run_with_logging_integration_verbose(
62+
tester: ApplicationTester, root_logger: logging.Logger
63+
) -> None:
64+
handler = CleoHandler(tester.io.output)
65+
root_logger.addHandler(handler)
4666

47-
tester = ApplicationTester(app)
48-
status_code = tester.execute(f"{cmd.name} -v")
67+
status_code = tester.execute("-v")
4968

5069
expected = (
5170
"This is an info log record\n"
@@ -57,13 +76,13 @@ def test_run_with_logging_integration_verbose(cmd: Command) -> None:
5776
assert tester.io.fetch_output() == expected
5877

5978

60-
@pytest.mark.parametrize("cmd", (Foo4Command(),))
61-
def test_run_with_logging_integration_very_verbose(cmd: Command) -> None:
62-
app = Application()
63-
app.add(cmd)
79+
def test_run_with_logging_integration_very_verbose(
80+
tester: ApplicationTester, root_logger: logging.Logger
81+
) -> None:
82+
handler = CleoHandler(tester.io.output)
83+
root_logger.addHandler(handler)
6484

65-
tester = ApplicationTester(app)
66-
status_code = tester.execute(f"{cmd.name} -vv")
85+
status_code = tester.execute("-vv")
6786

6887
expected = (
6988
"This is an debug log record\n"

0 commit comments

Comments
 (0)