Skip to content

Commit 11ff676

Browse files
committed
feat: Add extended standard logger support
CustomLogger to replace uneven print/trace solution. Add trace level support, set standard level to warning/error. Using --verbose or -v options, set the app logger to INFO, -vv set the app logger to DEBUG, -vvv set the app logger to TRACE status and last -vvvv to DEEP status. Signed-off-by: Helio Chissini de Castro <heliocastro@gmail.com>
1 parent 3ad3925 commit 11ff676

File tree

4 files changed

+127
-13
lines changed

4 files changed

+127
-13
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pip3 # Python compiled files
22
*.py[cod]
33

44
# virtualenv and other misc bits
5+
.venv
56
/src/*.egg-info
67
*.egg-info
78
/dist

src/python_inspector/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
pyinspector_settings = settings.Settings()
1414

1515
settings.create_cache_directory(pyinspector_settings.CACHE_THIRDPARTY_DIR)
16+

src/python_inspector/logging.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# ScanCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/python-inspector for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import logging
11+
from types import TracebackType
12+
from typing import Any, Optional, Tuple, Type, Union
13+
14+
# Add TRACE custom level to be third leve, as verbose will match
15+
# -v == logLevel INFO
16+
# -vv == logLevel DEBUG
17+
# -vvv == logLevel TRACE
18+
TRACE_LEVEL: int = 5
19+
DEEP_LEVEL: int = 4
20+
21+
22+
class CustomLogger(logging.Logger):
23+
def trace(
24+
self: logging.Logger,
25+
msg: Any,
26+
*args: Any,
27+
exc_info: Union[
28+
bool,
29+
BaseException,
30+
Tuple[Type[BaseException], BaseException, TracebackType],
31+
None,
32+
] = None,
33+
stack_info: bool = False,
34+
stacklevel: int = 1,
35+
extra: Optional[dict[str, Any]] = None,
36+
) -> None:
37+
if self.isEnabledFor(TRACE_LEVEL):
38+
self._log(
39+
TRACE_LEVEL,
40+
msg,
41+
args,
42+
exc_info=exc_info,
43+
extra=extra,
44+
stack_info=stack_info,
45+
stacklevel=stacklevel,
46+
)
47+
48+
def deep(
49+
self: logging.Logger,
50+
msg: Any,
51+
*args: Any,
52+
exc_info: Union[
53+
bool,
54+
BaseException,
55+
Tuple[Type[BaseException], BaseException, TracebackType],
56+
None,
57+
] = None,
58+
stack_info: bool = False,
59+
stacklevel: int = 1,
60+
extra: Optional[dict[str, Any]] = None,
61+
) -> None:
62+
if self.isEnabledFor(DEEP_LEVEL):
63+
self._log(
64+
DEEP_LEVEL,
65+
msg,
66+
args,
67+
exc_info=exc_info,
68+
extra=extra,
69+
stack_info=stack_info,
70+
stacklevel=stacklevel,
71+
)
72+
73+
74+
logging.Logger.trace = trace
75+
logging.Logger.deep = deep
76+
77+
78+
def setup_logger(level: str = "WARNING") -> None:
79+
"""
80+
Configures the logger for the 'python-inspector' application.
81+
82+
This function sets up a custom logging level, assigns a custom logger class,
83+
and configures the logger with the specified logging level. If no handlers are present,
84+
it adds a stream handler with a simple formatter.
85+
86+
Args:
87+
level (str): The logging level to set for the logger (e.g., 'DEBUG', 'INFO', 'WARNING', "TRACE").
88+
"""
89+
# Setup out trace level
90+
logging.addLevelName(TRACE_LEVEL, "TRACE")
91+
logging.addLevelName(DEEP_LEVEL, "DEEP")
92+
logging.setLoggerClass(CustomLogger)
93+
94+
_logger = logging.getLogger("python-inspector")
95+
_logger.setLevel(level)
96+
_logger.propagate = False
97+
98+
if not _logger.hasHandlers():
99+
handler = logging.StreamHandler()
100+
formatter = logging.Formatter("[%(levelname)s] %(message)s")
101+
handler.setFormatter(formatter)
102+
_logger.addHandler(handler)
103+
104+
105+
# Logger as a singleton
106+
logger: logging.Logger = logging.getLogger("python-inspector")

src/python_inspector/resolve_cli.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import click
1515

16+
from python_inspector import logging
1617
from python_inspector import pyinspector_settings
1718
from python_inspector import settings
1819
from python_inspector import utils_pypi
@@ -44,8 +45,7 @@ def print_version(ctx, param, value):
4445
metavar="REQUIREMENT-FILE",
4546
multiple=True,
4647
required=False,
47-
help="Path to pip requirements file listing thirdparty packages. "
48-
"This option can be used multiple times.",
48+
help="Path to pip requirements file listing thirdparty packages. This option can be used multiple times.",
4949
)
5050
@click.option(
5151
"-s",
@@ -96,8 +96,7 @@ def print_version(ctx, param, value):
9696
show_default=True,
9797
default=pyinspector_settings.INDEX_URL,
9898
multiple=True,
99-
help="PyPI simple index URL(s) to use in order of preference. "
100-
"This option can be used multiple times.",
99+
help="PyPI simple index URL(s) to use in order of preference. This option can be used multiple times.",
101100
)
102101
@click.option(
103102
"--json",
@@ -140,8 +139,7 @@ def print_version(ctx, param, value):
140139
"--use-cached-index",
141140
is_flag=True,
142141
hidden=True,
143-
help="Use cached on-disk PyPI simple package indexes "
144-
"and do not refetch package index if cache is present.",
142+
help="Use cached on-disk PyPI simple package indexes and do not refetch package index if cache is present.",
145143
)
146144
@click.option(
147145
"--use-pypi-json-api",
@@ -162,9 +160,10 @@ def print_version(ctx, param, value):
162160
"distribution is available then binary distributions are used",
163161
)
164162
@click.option(
163+
"-v",
165164
"--verbose",
166-
is_flag=True,
167-
help="Enable verbose debug output.",
165+
count=True,
166+
help="Increase verbosity: -v=INFO, -vv=DEBUG, -vvv=TRACE.",
168167
)
169168
@click.option(
170169
"-V",
@@ -175,9 +174,7 @@ def print_version(ctx, param, value):
175174
callback=print_version,
176175
help="Show the version and exit.",
177176
)
178-
@click.option(
179-
"--ignore-errors", is_flag=True, default=False, help="Ignore errors and continue execution."
180-
)
177+
@click.option("--ignore-errors", is_flag=True, default=False, help="Ignore errors and continue execution.")
181178
@click.help_option("-h", "--help")
182179
@click.option(
183180
"--generic-paths",
@@ -198,11 +195,11 @@ def resolve_dependencies(
198195
pdt_output,
199196
netrc_file,
200197
max_rounds,
198+
verbose,
201199
use_cached_index=False,
202200
use_pypi_json_api=False,
203201
analyze_setup_py_insecurely=False,
204202
prefer_source=False,
205-
verbose=TRACE,
206203
generic_paths=False,
207204
ignore_errors=False,
208205
):
@@ -237,6 +234,16 @@ def resolve_dependencies(
237234
click.secho("Only one of --json or --json-pdt can be used.", err=True)
238235
ctx.exit(1)
239236

237+
# Setup verbose level
238+
if verbose >= 3:
239+
logging.setup_logger("TRACE")
240+
elif verbose == 2:
241+
logging.setup_logger("DEBUG")
242+
elif verbose == 1:
243+
logging.setup_logger("INFO")
244+
else:
245+
logging.setup_logger()
246+
240247
options = get_pretty_options(ctx, generic_paths=generic_paths)
241248

242249
notice = (
@@ -268,7 +275,6 @@ def resolve_dependencies(
268275
max_rounds=max_rounds,
269276
use_cached_index=use_cached_index,
270277
use_pypi_json_api=use_pypi_json_api,
271-
verbose=verbose,
272278
analyze_setup_py_insecurely=analyze_setup_py_insecurely,
273279
printer=click.secho,
274280
prefer_source=prefer_source,

0 commit comments

Comments
 (0)