Skip to content

Commit bcf6245

Browse files
Format workflow error script output as Markdown
This commit updates the `print_workflow_run_errors.py` script to format its standard output using Markdown for improved readability when viewed in Markdown-aware environments. Changes include: - Job and step headings are now formatted as Markdown headers (H1-H4). - Workflow run and job URLs are presented as clickable Markdown links. - Actual log content (both grep results and last N lines) is enclosed in ```log ... ``` fenced code blocks. - Horizontal rules (`---`) are used to better separate sections. - Minor textual adjustments for clarity within the Markdown structure. The stderr output remains plain text for informational messages.
1 parent c20edf6 commit bcf6245

File tree

1 file changed

+47
-23
lines changed

1 file changed

+47
-23
lines changed

scripts/print_workflow_run_errors.py

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def parse_repo_url_arg(url_string):
148148
"--log-lines",
149149
type=int,
150150
default=100,
151-
help="Number of lines to print from the end of each failed step's log. Default: 100."
151+
help="Number of lines to print from the end of each failed step's log (if not using grep). Default: 100."
152152
)
153153
parser.add_argument(
154154
"--all-failed-steps",
@@ -299,15 +299,17 @@ def parse_repo_url_arg(url_string):
299299
print(f"Workflow run {run['id']} ({run.get('html_url', 'N/A')}) has conclusion '{run.get('conclusion')}', but no jobs matching pattern ('{args.job_pattern}') were found to have failed.")
300300
sys.exit(0)
301301

302-
print(f"\n--- Failed Jobs (matching pattern '{args.job_pattern}') for Workflow Run ID: {run['id']} ({run.get('html_url', 'No URL')}) ---\n")
302+
# Print summary of failed jobs to stderr
303+
sys.stderr.write("\nSummary of failed jobs matching criteria:\n")
304+
for job in failed_jobs_matching_criteria:
305+
sys.stderr.write(f" - {job['name']} (ID: {job['id']})\n")
306+
sys.stderr.write("\n") # Add a newline for separation before stdout details
307+
308+
print(f"\n# Detailed Logs for Failed Jobs (matching pattern '{args.job_pattern}') for Workflow Run ID: {run['id']} ([Run Link]({run.get('html_url', 'No URL')}))\n")
303309

304310
for job in failed_jobs_matching_criteria:
305-
print(f"==================================================================================")
306-
# Keep the job pattern in the individual job heading for clarity if needed, or remove if too verbose.
307-
# For now, let's assume it's clear from the main heading.
308-
print(f"Job: {job['name']} (ID: {job['id']}) - FAILED")
309-
print(f"Job URL: {job.get('html_url', 'N/A')}")
310-
print(f"==================================================================================")
311+
print(f"\n## Job: {job['name']} (ID: {job['id']}) - FAILED")
312+
print(f"[Job URL]({job.get('html_url', 'N/A')})\n")
311313

312314
job_logs = get_job_logs(args.token, job['id'])
313315
if not job_logs:
@@ -320,23 +322,41 @@ def parse_repo_url_arg(url_string):
320322
if step.get('conclusion') == 'failure':
321323
failed_steps_details.append(step)
322324

323-
if not failed_steps_details:
324-
print("\nNo specific failed steps found in job data, but job marked as failed. Printing last lines of full job log as fallback:\n")
325-
log_lines = job_logs.splitlines()
326-
for line in log_lines[-args.log_lines:]:
327-
print(line)
328-
print("\n--- End of log snippet for job ---")
325+
if not failed_steps_details: # No specific failed steps found in API, but job is failed
326+
print("\n**Note: No specific failed steps were identified in the job's metadata, but the job itself is marked as failed.**")
327+
log_lines_for_job_fallback = job_logs.splitlines()
328+
if args.grep_pattern:
329+
print(f"Displaying grep results for pattern '{args.grep_pattern}' with context {args.grep_context} from **entire job log**:")
330+
print("\n```log")
331+
try:
332+
process = subprocess.run(
333+
['grep', '-E', f"-C{args.grep_context}", args.grep_pattern],
334+
input="\n".join(log_lines_for_job_fallback), text=True, capture_output=True, check=False
335+
)
336+
if process.returncode == 0: print(process.stdout.strip())
337+
elif process.returncode == 1: print(f"No matches found for pattern '{args.grep_pattern}' in entire job log.")
338+
else: sys.stderr.write(f"Grep command failed on full job log: {process.stderr}\n")
339+
except FileNotFoundError: sys.stderr.write("Error: 'grep' not found, cannot process full job log with grep.\n")
340+
except Exception as e: sys.stderr.write(f"Grep error on full job log: {e}\n")
341+
print("```")
342+
else:
343+
print(f"Displaying last {args.log_lines} lines from **entire job log** as fallback:")
344+
print("\n```log")
345+
for line in log_lines_for_job_fallback[-args.log_lines:]:
346+
print(line)
347+
print("```")
348+
print("\n---") # Horizontal rule
329349
continue
330350

331-
print(f"\n--- Failed Steps in Job: {job['name']} ---")
351+
print(f"\n### Failed Steps in Job: {job['name']}")
332352
first_failed_step_logged = False
333353
for step in failed_steps_details:
334354
if not args.all_failed_steps and first_failed_step_logged:
335-
print(f"\n--- Skipping subsequent failed step: {step.get('name', 'Unnamed step')} (use --all-failed-steps to see all) ---")
355+
print(f"\n--- Skipping subsequent failed step: {step.get('name', 'Unnamed step')} (use --all-failed-steps to see all) ---") # Keep this as plain text for now
336356
break # Stop after the first failed step if not --all-failed-steps
337357

338358
step_name = step.get('name', 'Unnamed step')
339-
print(f"\n--- Step: {step_name} ---")
359+
print(f"\n#### Step: {step_name}")
340360

341361
# Crude log extraction:
342362
# Regex to match group start, attempting to capture the step name robustly
@@ -372,7 +392,8 @@ def parse_repo_url_arg(url_string):
372392
log_source_message = f"Could not isolate log for step '{step_name}'. Using entire job log"
373393

374394
if args.grep_pattern:
375-
print(f"{log_source_message} (grep results for pattern '{args.grep_pattern}' with context {args.grep_context}):")
395+
print(f"\n{log_source_message} (grep results for pattern `{args.grep_pattern}` with context {args.grep_context}):\n")
396+
print("```log")
376397
try:
377398
# Using subprocess to call grep
378399
# Pass log_to_process as stdin to grep
@@ -388,26 +409,29 @@ def parse_repo_url_arg(url_string):
388409
elif process.returncode == 1: # No match found
389410
print(f"No matches found for pattern '{args.grep_pattern}' in this log segment.")
390411
else: # Grep error
391-
sys.stderr.write(f"Grep command failed with error code {process.returncode}:\n{process.stderr}\n")
412+
# Print error within the log block if possible, or as a note if it's too disruptive
413+
print(f"Grep command failed with error code {process.returncode}. Stderr:\n{process.stderr}")
392414
except FileNotFoundError:
393415
sys.stderr.write("Error: 'grep' command not found. Please ensure it is installed and in your PATH to use --grep-pattern.\n")
394-
# Fallback to printing last N lines if grep is not found? Or just skip log? For now, skip.
395416
print("Skipping log display for this step as grep is unavailable.")
396417
except Exception as e:
397418
sys.stderr.write(f"An unexpected error occurred while running grep: {e}\n")
398419
print("Skipping log display due to an error with grep.")
420+
print("```")
399421
else:
400422
# Default behavior: print last N lines
401-
print(f"{log_source_message} (last {args.log_lines} lines):")
423+
print(f"\n{log_source_message} (last {args.log_lines} lines):\n")
424+
print("```log")
402425
# current_step_log_segment is a list of lines, log_lines_for_job is also a list of lines
403426
lines_to_print_from = current_step_log_segment if current_step_log_segment else log_lines_for_job
404427
for log_line in lines_to_print_from[-args.log_lines:]:
405428
print(log_line)
429+
print("```")
406430

407-
print(f"--- End of log for step: {step_name} ---")
431+
print(f"\n---") # Horizontal rule after each step's log
408432
first_failed_step_logged = True # Mark that we've logged at least one step
409433

410-
print(f"\n--- End of Failed Steps for Job: {job['name']} ---\n")
434+
print(f"\n---") # Horizontal rule after all steps for a job
411435

412436

413437
def get_latest_workflow_run(token, workflow_name, branch_name):

0 commit comments

Comments
 (0)