fix(logs): deep-review round 2 — a stack names code, not data, and survives a failed write

- A failure keeps its traceback unless it is yt-dlp's or our own StatedFailure (a
  message that is the whole story). A plain RuntimeError, RecursionError or OSError
  is also what a bug of ours raises, and its stack is the only lead.
- The stack is scrubbed link by link (logs.scrubbed_stack): a chained DownloadError
  under a DB error no longer prints yt-dlp's text unscrubbed.
- Engine hide_parameters: a DB error's message no longer carries the title or path
  it was writing.
- The download and edit failure lines go out even when their DB write fails.
- The worker's child_exit/crash/supervisor lines say download.job.failed only when
  the job failed there; a crash the DB will not record no longer ends the thread.
  Stopping a paused child is download.child.stopped, not a second abort.
- log_safe also covers generic ids with spaces, playlist titles and bare ids.
- The ffmpeg image steps log the failure kind, not the argv with its path.
- The consent contract accepts only the body of a plain consented_trace() check,
  and any level that may be TRACE.
- Tests for each; every one mutation-checked.
This commit is contained in:
2026-09-18 22:50:37 +02:00
parent 54d19cc386
commit 0b672ed6e7
14 changed files with 729 additions and 138 deletions
+52
View File
@@ -101,6 +101,44 @@ def test_the_source_url_does_not_travel_with_the_explanation(sink):
"WARNING: [generic] …: Falling back on generic information",
"my-private-holiday-clip",
),
# ...percent-decoded, so it can have spaces: `https://x/My%20Holiday%20in%20Crete.mp4`.
(
"ERROR: [generic] My Holiday in Crete: Unable to download webpage: HTTP Error 404",
"ERROR: [generic] …: Unable to download webpage: HTTP Error 404",
"Holiday",
),
(
"[generic] My Holiday in Crete: Downloading webpage",
"[generic] …: Downloading webpage",
"Holiday",
),
# A playlist URL is extracted too (`playlist_items: "1"`), and its title is printed.
(
"[youtube:tab] Playlist Our Wedding Videos: Downloading 1 items of 12",
"[youtube:tab] Playlist …: Downloading 1 items of 12",
"Wedding",
),
(
"[download] Finished downloading playlist: Our Wedding Videos",
"[download] Finished downloading playlist: …",
"Wedding",
),
(
"[download] Downloading playlist: Our Wedding Videos",
"[download] Downloading playlist: …",
"Wedding",
),
# An id quoted mid-sentence.
(
"hu subtitles not available for dQw4w9WgXcQ",
"hu subtitles not available for …",
"dQw4w9WgXcQ",
),
(
"[youtube:tab] Downloading just video dQw4w9WgXcQ because of --no-playlist",
"[youtube:tab] Downloading just video … because of --no-playlist",
"dQw4w9WgXcQ",
),
# `Destination` is yt-dlp's word, not an id; the path after it is built from the title.
(
"[download] Destination: /downloads/Some_Channel/Season_2024/A_Private_Title_a7.mp4",
@@ -125,6 +163,20 @@ def test_a_download_error_is_scrubbed_the_same_way_wherever_it_is_logged():
assert formats.log_safe(text) == "ERROR: Unsupported URL: example.org/…"
@pytest.mark.parametrize(
"line",
[
"nsig extraction failed: Some formats may be missing",
"[VideoConvertor] Converting video from webm to mp4; Destination: x",
"[download] 53.2% of ~ 12.00MiB at 1.20MiB/s ETA 00:05",
"[download] 100% of 12.00MiB in 00:00:03 at 3.90MiB/s",
],
)
def test_a_line_that_names_nothing_keeps_its_words(line):
"""The scrub takes out what names a download, and nothing an operator reads the line for."""
assert formats.log_safe(line) == line
def test_a_line_with_no_url_is_left_alone(sink):
formats._YDL_LOG.warning("nsig extraction failed: Some formats may be missing")