Skip to content

Commit 61bcdaf

Browse files
committed
Add tails of logs of failed packages to github comments
Snippets will be hidden under dropdown sections so as not to clutter UI. Snippets are limited to 20 lines, and if too many logs are generated, the report is truncated so that it fits into a single github comment. Signed-off-by: Ihar Hrachyshka <[email protected]>
1 parent 65bbe2d commit 61bcdaf

File tree

5 files changed

+66
-6
lines changed

5 files changed

+66
-6
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ pass the `--post-result` flag:
119119
$ nixpkgs-review pr --post-result 37242
120120
```
121121

122+
If you'd like to exclude log snippets for failed builds, add the `--no-logs`
123+
flag:
124+
125+
```console
126+
$ nixpkgs-review pr --post-result --no-logs 37242
127+
```
128+
122129
Instead of posting a PR comment, nixpkgs-review can also print the report to the
123130
terminal using the `--print-result` flag. This flag will work for the `rev` and
124131
`wip` command..

nixpkgs_review/cli/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ def pr_flags(
7171
action="store_true",
7272
help="Do not render the header in the markdown report",
7373
)
74+
pr_parser.add_argument(
75+
"--no-logs",
76+
action="store_true",
77+
help="Do not include build error log snippets in the markdown report",
78+
)
7479
pr_parser.set_defaults(func=pr_command)
7580
return pr_parser
7681

nixpkgs_review/cli/pr.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def pr_command(args: argparse.Namespace) -> str:
112112
extra_nixpkgs_config=args.extra_nixpkgs_config,
113113
num_parallel_evals=args.num_parallel_evals,
114114
show_header=not args.no_headers,
115+
show_logs=not args.no_logs,
115116
)
116117
contexts.append((pr, builddir.path, review.build_pr(pr)))
117118
except NixpkgsReviewError as e:

nixpkgs_review/report.py

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,18 @@
1212
from .nix import Attr
1313
from .utils import System, info, link, skipped, system_order_key, to_link, warn
1414

15+
# https://github.com/orgs/community/discussions/27190
16+
MAX_GITHUB_COMMENT_LENGTH = 65536
17+
1518

1619
def get_log_filename(a: Attr, system: str) -> str:
1720
return f"{a.name}-{system}.log"
1821

1922

23+
def get_log_dir(root: Path) -> Path:
24+
return root / "logs"
25+
26+
2027
def print_number(
2128
logs_dir: Path,
2229
system: str,
@@ -59,6 +66,28 @@ def html_pkgs_section(
5966
return res
6067

6168

69+
def get_file_tail(file: Path, lines: int = 20) -> str:
70+
try:
71+
with file.open("rb") as f:
72+
f.seek(0, os.SEEK_END)
73+
end = f.tell()
74+
f.seek(max(end - lines * 1024, 0), os.SEEK_SET)
75+
return "\n".join(
76+
f.read().decode("utf-8", errors="replace").splitlines()[-lines:]
77+
)
78+
except OSError:
79+
return ""
80+
81+
82+
def html_logs_section(logs_dir: Path, packages: list[Attr], system: str) -> str:
83+
res = ""
84+
for pkg in packages:
85+
tail = get_file_tail(logs_dir / get_log_filename(pkg, system))
86+
if tail:
87+
res += f"<details>\n<summary>{pkg.name}</summary>\n<pre>{tail}</pre>\n</details>\n"
88+
return res
89+
90+
6291
class LazyDirectory:
6392
def __init__(self, path: Path) -> None:
6493
self.path = path
@@ -112,7 +141,7 @@ def write_error_logs(
112141
*,
113142
max_workers: int | None = 1,
114143
) -> None:
115-
logs = LazyDirectory(directory.joinpath("logs"))
144+
logs = LazyDirectory(get_log_dir(directory))
116145
results = LazyDirectory(directory.joinpath("results"))
117146
failed_results = LazyDirectory(directory.joinpath("failed_results"))
118147

@@ -242,11 +271,13 @@ def __init__(
242271
skip_packages: set[str],
243272
skip_packages_regex: list[Pattern[str]],
244273
show_header: bool = True,
274+
show_logs: bool = False,
245275
max_workers: int | None = 1,
246276
*,
247277
checkout: Literal["merge", "commit"] = "merge",
248278
) -> None:
249279
self.show_header = show_header
280+
self.show_logs = show_logs
250281
self.max_workers = max_workers
251282
self.attrs = attrs_per_system
252283
self.checkout = checkout
@@ -272,10 +303,10 @@ def built_packages(self) -> dict[System, list[str]]:
272303
}
273304

274305
def write(self, directory: Path, pr: int | None) -> None:
275-
directory.joinpath("report.md").write_text(self.markdown(pr))
276-
directory.joinpath("report.json").write_text(self.json(pr))
277-
306+
# write logs first because snippets from them may be needed for the report
278307
write_error_logs(self.attrs, directory, max_workers=self.max_workers)
308+
directory.joinpath("report.md").write_text(self.markdown(directory, pr))
309+
directory.joinpath("report.json").write_text(self.json(pr))
279310

280311
def succeeded(self) -> bool:
281312
"""Whether the report is considered a success or a failure"""
@@ -301,7 +332,7 @@ def json(self, pr: int | None) -> str:
301332
indent=4,
302333
)
303334

304-
def markdown(self, pr: int | None) -> str:
335+
def markdown(self, root: Path, pr: int | None) -> str:
305336
msg = ""
306337
if self.show_header:
307338
msg += "## `nixpkgs-review` result\n\n"
@@ -346,6 +377,19 @@ def markdown(self, pr: int | None) -> str:
346377
)
347378
msg += html_pkgs_section(":white_check_mark:", report.built, "built")
348379

380+
if self.show_logs:
381+
for system, report in self.system_reports.items():
382+
if not report.failed:
383+
continue
384+
full_msg = msg
385+
full_msg += "\n---\n"
386+
full_msg += f"### Error logs: `{system}`\n"
387+
full_msg += html_logs_section(get_log_dir(root), report.failed, system)
388+
# if the final message won't fit a single github comment, stop
389+
if len(full_msg) > MAX_GITHUB_COMMENT_LENGTH:
390+
break
391+
msg = full_msg
392+
349393
return msg
350394

351395
def print_console(self, root: Path, pr: int | None) -> None:
@@ -354,7 +398,7 @@ def print_console(self, root: Path, pr: int | None) -> None:
354398
info("\nLink to currently reviewing PR:")
355399
link(to_link(pr_url, pr_url))
356400

357-
logs_dir = root / "logs"
401+
logs_dir = get_log_dir(root)
358402
for system, report in self.system_reports.items():
359403
info(f"--------- Report for '{system}' ---------")
360404
p = functools.partial(print_number, logs_dir, system)

nixpkgs_review/review.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def __init__(
112112
sandbox: bool = False,
113113
num_parallel_evals: int = 1,
114114
show_header: bool = True,
115+
show_logs: bool = False,
115116
) -> None:
116117
if skip_packages_regex is None:
117118
skip_packages_regex = []
@@ -149,6 +150,7 @@ def __init__(
149150
self.extra_nixpkgs_config = extra_nixpkgs_config
150151
self.num_parallel_evals = num_parallel_evals
151152
self.show_header = show_header
153+
self.show_logs = show_logs
152154

153155
def _process_aliases_for_systems(self, system: str) -> set[str]:
154156
match system:
@@ -408,6 +410,7 @@ def start_review(
408410
skip_packages=self.skip_packages,
409411
skip_packages_regex=self.skip_packages_regex,
410412
show_header=self.show_header,
413+
show_logs=self.show_logs,
411414
# we don't use self.num_parallel_evals here since its choice
412415
# is mainly capped by available RAM
413416
max_workers=min(32, os.cpu_count() or 1), # 'None' assumes IO tasks

0 commit comments

Comments
 (0)