Skip to content

Commit a42bf91

Browse files
author
Taniya Mathur
committed
Add --verbose flag for detailed error reporting in publish.py
- Add --verbose/-v flag to enable detailed error output for debugging - Implement comprehensive error logging with log_verbose() and log_error_details() methods - Track build errors in build_errors list for summary reporting - Show detailed SAM build/package command output and error messages in verbose mode - Add error summary with preview for non-verbose mode - Include exception tracebacks in verbose mode for better debugging - Update unit tests to handle new verbose parameter and build_errors attribute - Resolves issue where build failures only showed generic 'Failed to build' message Example usage: python publish.py bucket prefix region --verbose This addresses the issue where SAM build failures like Python version mismatches were not visible to users, making debugging difficult.
1 parent 4070431 commit a42bf91

File tree

2 files changed

+88
-11
lines changed

2 files changed

+88
-11
lines changed

lib/idp_common_pkg/tests/unit/test_publish.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@ def test_init_default_values(self):
4949
assert publisher.bucket is None
5050
assert publisher.prefix_and_version is None
5151
assert publisher.version is None
52-
assert publisher.public_sample_udop_model == ""
53-
assert publisher.public is False
54-
assert publisher.main_template == "idp-main.yaml"
55-
assert publisher.use_container_flag == ""
56-
assert publisher.stat_cmd is None
57-
assert publisher.s3_client is None
58-
assert publisher.cf_client is None
59-
assert publisher._print_lock is not None
52+
assert publisher.verbose is False
53+
assert publisher.build_errors == []
54+
55+
def test_init_verbose_mode(self):
56+
"""Test that IDPPublisher initializes correctly with verbose mode enabled"""
57+
publisher = IDPPublisher(verbose=True)
58+
59+
assert publisher.verbose is True
60+
assert publisher.build_errors == []
6061

6162

6263
@pytest.mark.unit

publish.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,55 @@
3939

4040

4141
class IDPPublisher:
42-
def __init__(self):
42+
def __init__(self, verbose=False):
4343
self.console = Console()
44+
self.verbose = verbose
4445
self.bucket_basename = None
4546
self.prefix = None
4647
self.region = None
4748
self.acl = None
4849
self.bucket = None
4950
self.prefix_and_version = None
5051
self.version = None
52+
self.build_errors = [] # Track build errors for verbose reporting
53+
54+
def log_verbose(self, message, style="dim"):
55+
"""Log verbose messages if verbose mode is enabled"""
56+
if self.verbose:
57+
self.console.print(f"[{style}]{message}[/{style}]")
58+
59+
def log_error_details(self, component, error_output):
60+
"""Log detailed error information and store for summary"""
61+
error_info = {
62+
"component": component,
63+
"error": error_output
64+
}
65+
self.build_errors.append(error_info)
66+
67+
if self.verbose:
68+
self.console.print(f"[red]❌ {component} build failed:[/red]")
69+
self.console.print(f"[red]{error_output}[/red]")
70+
else:
71+
self.console.print(f"[red]❌ {component} build failed (use --verbose for details)[/red]")
72+
73+
def print_error_summary(self):
74+
"""Print summary of all build errors"""
75+
if not self.build_errors:
76+
return
77+
78+
self.console.print("\n[red]❌ Build Error Summary:[/red]")
79+
for i, error_info in enumerate(self.build_errors, 1):
80+
self.console.print(f"\n[red]{i}. {error_info['component']}:[/red]")
81+
if self.verbose:
82+
self.console.print(f"[red]{error_info['error']}[/red]")
83+
else:
84+
# Show first few lines of error for non-verbose mode
85+
error_lines = error_info['error'].strip().split('\n')
86+
preview_lines = error_lines[:3] # Show first 3 lines
87+
for line in preview_lines:
88+
self.console.print(f"[red] {line}[/red]")
89+
if len(error_lines) > 3:
90+
self.console.print(f"[dim] ... ({len(error_lines) - 3} more lines, use --verbose for full output)[/dim]")
5191
self.public_sample_udop_model = ""
5292
self.public = False
5393
self.main_template = "idp-main.yaml"
@@ -579,23 +619,29 @@ def build_and_package_template(self, directory):
579619
shutil.rmtree(cache_dir)
580620

581621
sam_build_start = time.time()
622+
self.log_verbose(f"Running SAM build command: {' '.join(cmd)}")
582623
result = subprocess.run(
583624
cmd, cwd=abs_directory, capture_output=True, text=True
584625
)
585626
sam_build_time = time.time() - sam_build_start
586627

587628
if result.returncode != 0:
588629
# If cached build fails, try without cache
630+
self.log_verbose(f"Cached build failed for {directory}, retrying without cache")
589631
self.console.print(
590632
f"[yellow]Cached build failed for {directory}, retrying without cache[/yellow]"
591633
)
592634
cmd_no_cache = [c for c in cmd if c != "--cached"]
593635
sam_build_start = time.time()
636+
self.log_verbose(f"Running SAM build command (no cache): {' '.join(cmd_no_cache)}")
594637
result = subprocess.run(
595638
cmd_no_cache, cwd=abs_directory, capture_output=True, text=True
596639
)
597640
sam_build_time = time.time() - sam_build_start
598641
if result.returncode != 0:
642+
# Log detailed error information
643+
error_output = f"STDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
644+
self.log_error_details(f"SAM build for {directory}", error_output)
599645
return False
600646

601647
# Package the template
@@ -620,12 +666,16 @@ def build_and_package_template(self, directory):
620666
]
621667

622668
sam_package_start = time.time()
669+
self.log_verbose(f"Running SAM package command: {' '.join(cmd)}")
623670
result = subprocess.run(
624671
cmd, cwd=abs_directory, capture_output=True, text=True
625672
)
626673
sam_package_time = time.time() - sam_package_start
627674

628675
if result.returncode != 0:
676+
# Log detailed error information
677+
error_output = f"STDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
678+
self.log_error_details(f"SAM package for {directory}", error_output)
629679
return False
630680

631681
# Log timing information
@@ -726,6 +776,11 @@ def build_patterns_concurrently(self, max_workers=None):
726776
progress.update(main_task, completed=completed)
727777

728778
except Exception as e:
779+
# Log detailed error information
780+
import traceback
781+
error_output = f"Exception: {str(e)}\n\nTraceback:\n{traceback.format_exc()}"
782+
self.log_error_details(f"Pattern {pattern} build exception", error_output)
783+
729784
progress.update(
730785
pattern_tasks[pattern],
731786
description=f"[red]{pattern}[/red] - Error: {str(e)[:30]}...",
@@ -815,6 +870,11 @@ def build_options_concurrently(self, max_workers=None):
815870
progress.update(main_task, completed=completed)
816871

817872
except Exception as e:
873+
# Log detailed error information
874+
import traceback
875+
error_output = f"Exception: {str(e)}\n\nTraceback:\n{traceback.format_exc()}"
876+
self.log_error_details(f"Option {option} build exception", error_output)
877+
818878
progress.update(
819879
option_tasks[option],
820880
description=f"[red]{option}[/red] - Error: {str(e)[:30]}...",
@@ -1020,8 +1080,11 @@ def build_main_template(self, webui_zipfile):
10201080
self.prefix_and_version,
10211081
]
10221082

1023-
result = subprocess.run(cmd)
1083+
self.log_verbose(f"Running main template SAM package command: {' '.join(cmd)}")
1084+
result = subprocess.run(cmd, capture_output=True, text=True)
10241085
if result.returncode != 0:
1086+
error_output = f"STDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
1087+
self.log_error_details("Main template SAM package", error_output)
10251088
self.console.print("[red]Error packaging main template[/red]")
10261089
sys.exit(1)
10271090
finally:
@@ -1171,9 +1234,12 @@ def run(self, args):
11711234
patterns_time = time.time() - patterns_start
11721235

11731236
if not patterns_success:
1237+
self.print_error_summary()
11741238
self.console.print(
11751239
"[red]❌ Error: Failed to build one or more patterns[/red]"
11761240
)
1241+
if not self.verbose:
1242+
self.console.print("[dim]Use --verbose flag for detailed error information[/dim]")
11771243
sys.exit(1)
11781244

11791245
# Build options concurrently
@@ -1185,9 +1251,12 @@ def run(self, args):
11851251
options_time = time.time() - options_start
11861252

11871253
if not options_success:
1254+
self.print_error_summary()
11881255
self.console.print(
11891256
"[red]❌ Error: Failed to build one or more options[/red]"
11901257
)
1258+
if not self.verbose:
1259+
self.console.print("[dim]Use --verbose flag for detailed error information[/dim]")
11911260
sys.exit(1)
11921261

11931262
total_build_time = time.time() - start_time
@@ -1244,6 +1313,9 @@ def main(
12441313
"--max-workers",
12451314
help="Maximum number of concurrent workers (default: auto-detect)",
12461315
),
1316+
verbose: bool = typer.Option(
1317+
False, "--verbose", "-v", help="Enable verbose output for debugging"
1318+
),
12471319
):
12481320
"""
12491321
[bold cyan]GenAI IDP Publisher[/bold cyan]
@@ -1262,7 +1334,11 @@ def main(
12621334
if max_workers is not None:
12631335
args.extend(["--max-workers", str(max_workers)])
12641336

1265-
publisher = IDPPublisher()
1337+
publisher = IDPPublisher(verbose=verbose)
1338+
1339+
if verbose:
1340+
console.print("[dim]Verbose mode enabled - detailed error output will be shown[/dim]")
1341+
12661342
publisher.run(args)
12671343

12681344
except KeyboardInterrupt:

0 commit comments

Comments
 (0)