fix(logs): deep-review round 3 — a failure nobody could record is one scrubbed line

- The download and edit failure lines go back after their DB write: a job the
  write could not fail is not a failed job, and the worker's child_exit line
  already says it failed.
- The download child's runner is the last stop before Python's hook: an
  escaping exception is one download.child.failed line with its chain scrubbed,
  then exit 1, instead of a raw stderr traceback carrying the URL.
- A crash the database will not record carries its stack once, and the
  fail-write line has an event code.
- The job's own generic-extractor name is taken out as a literal (formats.hiding),
  so brackets and colons in a direct link's file name no longer leak; the
  pattern stays as the fallback. `Downloading just the video <id>` is covered.
- Tests: scrubbed_stack's cause, suppress, unprintable and text-format paths,
  the image steps' failure kinds, the watchdog error through the real
  _fail_running, exact poster/storyboard messages, ffmpeg skip marker.
This commit is contained in:
2026-09-18 23:44:42 +02:00
parent 0b672ed6e7
commit 89c77ae6b5
9 changed files with 407 additions and 81 deletions
+33 -2
View File
@@ -134,9 +134,15 @@ def test_the_source_url_does_not_travel_with_the_explanation(sink):
"hu subtitles not available for …",
"dQw4w9WgXcQ",
),
# `watch?v=…&list=…` under `noplaylist` (`_yes_playlist`), and the multifeed shape.
(
"[youtube:tab] Downloading just video dQw4w9WgXcQ because of --no-playlist",
"[youtube:tab] Downloading just video … because of --no-playlist",
"[youtube:tab] Downloading just the video dQw4w9WgXcQ because of --no-playlist",
"[youtube:tab] Downloading just the video … because of --no-playlist",
"dQw4w9WgXcQ",
),
(
"[youtube] Downloading just video dQw4w9WgXcQ because of --no-playlist",
"[youtube] 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.
@@ -155,6 +161,31 @@ def test_what_names_the_download_comes_out_of_real_ytdlp_shapes(sink, line, kept
assert gone not in json.dumps(obj)
@pytest.mark.parametrize(
("url", "name"),
[
("https://example.org/v/Wedding%20%5B2024%5D.mp4", "Wedding [2024]"),
("https://example.org/v/Our%20Trip%3A%20Day%201.mp4", "Our Trip: Day 1"),
],
)
def test_the_jobs_own_generic_name_is_taken_out_whatever_it_holds(url, name):
"""Brackets and `: ` bound no pattern; the job knows its URL, so the exact name goes."""
line = f"ERROR: [generic] {name}: Unable to download webpage: HTTP Error 404: Not Found"
assert formats.generic_id(url) == name
with formats.hiding(url):
assert formats.log_safe(line) == (
"ERROR: [generic] …: Unable to download webpage: HTTP Error 404: Not Found"
)
def test_the_generic_name_is_yt_dlps_own():
from yt_dlp.extractor.common import InfoExtractor
for url in ("https://x.org/a/My%20Holiday.mp4", "https://x.org/a/clip/", "https://x.org/b"):
assert formats.generic_id(url) == InfoExtractor._generic_id(url)
def test_a_download_error_is_scrubbed_the_same_way_wherever_it_is_logged():
"""yt-dlp raises the text it has just logged, and the pipeline logs the exception again one
line later (`download.job.failed`). The same scrub, or that line puts the URL back."""