Skip to content

Commit 408075b

Browse files
committed
refactor: improve console logging for commands
Signed-off-by: Demolus13 <[email protected]>
1 parent 92e9032 commit 408075b

File tree

9 files changed

+626
-88
lines changed

9 files changed

+626
-88
lines changed

src/macaron/__main__.py

Lines changed: 121 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import macaron
1717
from macaron.config.defaults import create_defaults, load_defaults
1818
from macaron.config.global_config import global_config
19+
from macaron.console import access_handler
1920
from macaron.errors import ConfigurationError
2021
from macaron.output_reporter.reporter import HTMLReporter, JSONReporter, PolicyReporter
2122
from macaron.policy_engine.policy_engine import run_policy_engine, show_prelude
@@ -59,7 +60,8 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
5960
if analyzer_single_args.provenance_expectation is not None:
6061
if not os.path.exists(analyzer_single_args.provenance_expectation):
6162
logger.critical(
62-
'The provenance expectation file "%s" does not exist.', analyzer_single_args.provenance_expectation
63+
'The provenance expectation file "%s" does not exist.',
64+
analyzer_single_args.provenance_expectation,
6365
)
6466
sys.exit(os.EX_OSFILE)
6567
global_config.load_expectation_files(analyzer_single_args.provenance_expectation)
@@ -68,7 +70,8 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
6870
if analyzer_single_args.python_venv is not None:
6971
if not os.path.exists(analyzer_single_args.python_venv):
7072
logger.critical(
71-
'The Python virtual environment path "%s" does not exist.', analyzer_single_args.python_venv
73+
'The Python virtual environment path "%s" does not exist.',
74+
analyzer_single_args.python_venv,
7275
)
7376
sys.exit(os.EX_OSFILE)
7477
global_config.load_python_venv(analyzer_single_args.python_venv)
@@ -91,7 +94,10 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
9194
else:
9295
user_provided_local_maven_repo = analyzer_single_args.local_maven_repo
9396
if not os.path.isdir(user_provided_local_maven_repo):
94-
logger.error("The user provided local Maven repo at %s is not valid.", user_provided_local_maven_repo)
97+
logger.error(
98+
"The user provided local Maven repo at %s is not valid.",
99+
user_provided_local_maven_repo,
100+
)
95101
sys.exit(os.EX_USAGE)
96102

97103
global_config.local_maven_repo = user_provided_local_maven_repo
@@ -107,7 +113,8 @@ def analyze_slsa_levels_single(analyzer_single_args: argparse.Namespace) -> None
107113
lstrip_blocks=True,
108114
)
109115
html_reporter = HTMLReporter(
110-
env=custom_jinja_env, target_template=os.path.basename(analyzer_single_args.template_path)
116+
env=custom_jinja_env,
117+
target_template=os.path.basename(analyzer_single_args.template_path),
111118
)
112119
if not html_reporter.template:
113120
logger.error("Exiting because the custom template cannot be found.")
@@ -203,8 +210,10 @@ def verify_policy(verify_policy_args: argparse.Namespace) -> int:
203210

204211
result = run_policy_engine(verify_policy_args.database, policy_content)
205212
vsa = generate_vsa(policy_content=policy_content, policy_result=result)
213+
rich_handler = access_handler.get_handler()
206214
if vsa is not None:
207215
vsa_filepath = os.path.join(global_config.output_path, "vsa.intoto.jsonl")
216+
rich_handler.update_vsa(vsa_filepath)
208217
logger.info(
209218
"Generating the Verification Summary Attestation (VSA) to %s.",
210219
os.path.relpath(vsa_filepath, os.getcwd()),
@@ -218,8 +227,12 @@ def verify_policy(verify_policy_args: argparse.Namespace) -> int:
218227
file.write(json.dumps(vsa))
219228
except OSError as err:
220229
logger.error(
221-
"Could not generate the VSA to %s. Error: %s", os.path.relpath(vsa_filepath, os.getcwd()), err
230+
"Could not generate the VSA to %s. Error: %s",
231+
os.path.relpath(vsa_filepath, os.getcwd()),
232+
err,
222233
)
234+
else:
235+
rich_handler.update_vsa("No VSA generated.")
223236

224237
policy_reporter = PolicyReporter()
225238
policy_reporter.generate(global_config.output_path, result)
@@ -245,16 +258,23 @@ def find_source(find_args: argparse.Namespace) -> int:
245258

246259
def perform_action(action_args: argparse.Namespace) -> None:
247260
"""Perform the indicated action of Macaron."""
261+
rich_handler = access_handler.get_handler()
248262
match action_args.action:
249263
case "dump-defaults":
264+
if not action_args.disable_rich_output:
265+
rich_handler.start("dump-defaults")
250266
# Create the defaults.ini file in the output dir and exit.
251267
create_defaults(action_args.output_dir, os.getcwd())
252268
sys.exit(os.EX_OK)
253269

254270
case "verify-policy":
271+
if not action_args.disable_rich_output:
272+
rich_handler.start("verify-policy")
255273
sys.exit(verify_policy(action_args))
256274

257275
case "analyze":
276+
if not action_args.disable_rich_output:
277+
rich_handler.start("analyze")
258278
if not global_config.gh_token:
259279
logger.error("GitHub access token not set.")
260280
sys.exit(os.EX_USAGE)
@@ -272,6 +292,8 @@ def perform_action(action_args: argparse.Namespace) -> None:
272292
analyze_slsa_levels_single(action_args)
273293

274294
case "find-source":
295+
if not action_args.disable_rich_output:
296+
rich_handler.start("find-source")
275297
try:
276298
for git_service in GIT_SERVICES:
277299
git_service.load_defaults()
@@ -345,6 +367,14 @@ def main(argv: list[str] | None = None) -> None:
345367
action="store_true",
346368
)
347369

370+
main_parser.add_argument(
371+
"-dro",
372+
"--disable-rich-output",
373+
default=False,
374+
help="Disable Rich UI output",
375+
action="store_true",
376+
)
377+
348378
main_parser.add_argument(
349379
"-o",
350380
"--output-dir",
@@ -483,7 +513,10 @@ def main(argv: list[str] | None = None) -> None:
483513
)
484514

485515
# Dump the default values.
486-
sub_parser.add_parser(name="dump-defaults", description="Dumps the defaults.ini file to the output directory.")
516+
sub_parser.add_parser(
517+
name="dump-defaults",
518+
description="Dumps the defaults.ini file to the output directory.",
519+
)
487520

488521
# Verify the Datalog policy.
489522
vp_parser = sub_parser.add_parser(name="verify-policy")
@@ -521,65 +554,94 @@ def main(argv: list[str] | None = None) -> None:
521554
main_parser.print_help()
522555
sys.exit(os.EX_USAGE)
523556

524-
if args.verbose:
525-
log_level = logging.DEBUG
526-
log_format = "%(asctime)s [%(name)s:%(funcName)s:%(lineno)d] [%(levelname)s] %(message)s"
527-
else:
528-
log_level = logging.INFO
529-
log_format = "%(asctime)s [%(levelname)s] %(message)s"
530-
531557
# Set global logging config. We need the stream handler for the initial
532558
# output directory checking log messages.
533-
st_handler = logging.StreamHandler(sys.stdout)
534-
logging.basicConfig(format=log_format, handlers=[st_handler], force=True, level=log_level)
535-
536-
# Set the output directory.
537-
if not args.output_dir:
538-
logger.error("The output path cannot be empty. Exiting ...")
539-
sys.exit(os.EX_USAGE)
559+
st_handler: logging.StreamHandler = logging.StreamHandler(sys.stdout)
560+
rich_handler: logging.Handler = logging.Handler()
561+
if args.disable_rich_output:
562+
if args.verbose:
563+
log_level = logging.DEBUG
564+
log_format = "%(asctime)s [%(name)s:%(funcName)s:%(lineno)d] [%(levelname)s] %(message)s"
565+
else:
566+
log_level = logging.INFO
567+
log_format = "%(asctime)s [%(levelname)s] %(message)s"
568+
st_handler = logging.StreamHandler(sys.stdout)
569+
logging.basicConfig(format=log_format, handlers=[st_handler], force=True, level=log_level)
570+
else:
571+
if args.verbose:
572+
log_level = logging.DEBUG
573+
log_format = "%(asctime)s [%(name)s:%(funcName)s:%(lineno)d] %(message)s"
574+
else:
575+
log_level = logging.INFO
576+
log_format = "%(asctime)s %(message)s"
577+
rich_handler = access_handler.set_handler(args.verbose)
578+
logging.basicConfig(format=log_format, handlers=[rich_handler], force=True, level=log_level)
579+
580+
try:
581+
# Set the output directory.
582+
if not args.output_dir:
583+
logger.error("The output path cannot be empty. Exiting ...")
584+
sys.exit(os.EX_USAGE)
540585

541-
if os.path.isfile(args.output_dir):
542-
logger.error("The output directory already exists. Exiting ...")
543-
sys.exit(os.EX_USAGE)
586+
if os.path.isfile(args.output_dir):
587+
logger.error("The output directory already exists. Exiting ...")
588+
sys.exit(os.EX_USAGE)
544589

545-
if os.path.isdir(args.output_dir):
546-
logger.info("Setting the output directory to %s", os.path.relpath(args.output_dir, os.getcwd()))
547-
else:
548-
logger.info("No directory at %s. Creating one ...", os.path.relpath(args.output_dir, os.getcwd()))
549-
os.makedirs(args.output_dir)
550-
551-
# Add file handler to the root logger. Remove stream handler from the
552-
# root logger to prevent dependencies printing logs to stdout.
553-
debug_log_path = os.path.join(args.output_dir, "debug.log")
554-
log_file_handler = logging.FileHandler(debug_log_path, "w")
555-
log_file_handler.setFormatter(logging.Formatter(log_format))
556-
logging.getLogger().removeHandler(st_handler)
557-
logging.getLogger().addHandler(log_file_handler)
558-
559-
# Add StreamHandler to the Macaron logger only.
560-
mcn_logger = logging.getLogger("macaron")
561-
mcn_logger.addHandler(st_handler)
562-
563-
logger.info("The logs will be stored in debug.log")
564-
565-
# Set Macaron's global configuration.
566-
# The path to provenance expectation files will be updated if
567-
# set through analyze sub-command.
568-
global_config.load(
569-
macaron_path=macaron.MACARON_PATH,
570-
output_path=args.output_dir,
571-
build_log_path=os.path.join(args.output_dir, "build_log"),
572-
debug_level=log_level,
573-
local_repos_path=args.local_repos_path,
574-
resources_path=os.path.join(macaron.MACARON_PATH, "resources"),
575-
)
590+
if os.path.isdir(args.output_dir):
591+
logger.info(
592+
"Setting the output directory to %s",
593+
os.path.relpath(args.output_dir, os.getcwd()),
594+
)
595+
else:
596+
logger.info(
597+
"No directory at %s. Creating one ...",
598+
os.path.relpath(args.output_dir, os.getcwd()),
599+
)
600+
os.makedirs(args.output_dir)
601+
602+
# Add file handler to the root logger. Remove stream handler from the
603+
# root logger to prevent dependencies printing logs to stdout.
604+
debug_log_path = os.path.join(args.output_dir, "debug.log")
605+
log_file_handler = logging.FileHandler(debug_log_path, "w")
606+
log_file_handler.setFormatter(logging.Formatter(log_format))
607+
if args.disable_rich_output:
608+
logging.getLogger().removeHandler(st_handler)
609+
else:
610+
logging.getLogger().removeHandler(rich_handler)
611+
logging.getLogger().addHandler(log_file_handler)
612+
613+
# Add StreamHandler to the Macaron logger only.
614+
mcn_logger = logging.getLogger("macaron")
615+
if args.disable_rich_output:
616+
mcn_logger.addHandler(st_handler)
617+
else:
618+
mcn_logger.addHandler(rich_handler)
619+
620+
logger.info("The logs will be stored in debug.log")
621+
622+
# Set Macaron's global configuration.
623+
# The path to provenance expectation files will be updated if
624+
# set through analyze sub-command.
625+
global_config.load(
626+
macaron_path=macaron.MACARON_PATH,
627+
output_path=args.output_dir,
628+
build_log_path=os.path.join(args.output_dir, "build_log"),
629+
debug_level=log_level,
630+
local_repos_path=args.local_repos_path,
631+
resources_path=os.path.join(macaron.MACARON_PATH, "resources"),
632+
)
576633

577-
# Load the default values from defaults.ini files.
578-
if not load_defaults(args.defaults_path):
579-
logger.error("Exiting because the defaults configuration could not be loaded.")
580-
sys.exit(os.EX_NOINPUT)
634+
# Load the default values from defaults.ini files.
635+
if not load_defaults(args.defaults_path):
636+
logger.error("Exiting because the defaults configuration could not be loaded.")
637+
sys.exit(os.EX_NOINPUT)
581638

582-
perform_action(args)
639+
perform_action(args)
640+
finally:
641+
if args.disable_rich_output:
642+
st_handler.close()
643+
else:
644+
rich_handler.close()
583645

584646

585647
def _get_token_from_dict_or_env(token: str, token_dict: dict[str, str]) -> str:

0 commit comments

Comments
 (0)