fix(logs): deep-review round 1 — say each failure once, and scrub it everywhere
- yt-dlp's text is scrubbed wherever it is logged, not only in the adapter:
`formats.log_safe` takes out the URL, the `[extractor] <id>:` prefix and
file paths, and the job-failed, refused, edit-failed and backfill lines use
it. Done lines log the asset id, not the title-built path. The PII contract
now bans `rel`/`rel_path`/`title`.
- A traceback goes with a download/edit failure only when it is our own code
failing; for yt-dlp's DownloadError, our RuntimeErrors and OSErrors its foot
is the unscrubbed message.
- yt-dlp's `error` maps to WARNING: it fires on attempts the fallback client
set recovers from; the job's verdict is `download.job.failed`.
- The sink groups a bare `%s` pass-through by its text (numbers folded), and
a process's last batch closes the repeat window, so a short-lived download
child still reports what it dropped.
- Worker-side failures (stall, wall-clock, no asset, cached asset error) log
`download.job.failed` with a cause; they left no line at all before. The
crash, supervisor-error and non-zero-exit lines carry the code too.
- An edit failure is one coded line in `_fail_edit`, for every path.
- `logs.get_logger("__main__")` resolves to the `-m` module, so the worker
and the download child keep their names.
- TRACE lines carrying user content are gated on `logs.consented_trace()`,
not on the process level; a contract test pins it.
- The OAuth token refresh goes through `_upstream_failure`; transport errors
carry reason `transport`. Subscribe recognises a duplicate by reason code
only (the prose fallback was dead).
- Once-only guards for a stale sysconfig override and a refusing HLS session
directory; item_detail splits Plex failures from our own parse bugs.
- The playlist breaker line names the playlist and the action; comments that
were false (runner noqa, `_merge_vtt`, sink grouping) are corrected.
This commit is contained in:
@@ -165,9 +165,10 @@ def discover_channels(
|
||||
user.yt_channel_id = own
|
||||
db.commit()
|
||||
except YouTubeError as exc:
|
||||
# No traceback anywhere in this file's YouTube handlers: `_upstream_failure` already
|
||||
# logged the structured `youtube.api.error`, and the message carries method, path,
|
||||
# status and reason. The stack under it is this `except` and the client call.
|
||||
# No traceback anywhere in this file's YouTube handlers: for an upstream answer
|
||||
# `_upstream_failure` already logged the structured `youtube.api.error`, and the
|
||||
# message names the call, its status and its reason (or, with no answer at all, the
|
||||
# transport failure). The stack under it is this `except` and the client call.
|
||||
log.warning(
|
||||
"discovery: own-channel resolve failed: %s",
|
||||
exc,
|
||||
@@ -575,16 +576,11 @@ def subscribe(
|
||||
# subscription resync fills in the resource id. Any other YouTube error is a real
|
||||
# problem — surface it with a clear message (422, not a 502 that the client would
|
||||
# mistake for a transient "connection lost").
|
||||
# On the reason code, not on the prose: `_upstream_failure` builds the message from
|
||||
# the code now, and a message YouTube rewords would silently turn "already following"
|
||||
# back into a 422 the user cannot act on. The substring check stays beside it for a
|
||||
# body that carried no reason at all (`<none>` / `<unparseable>`).
|
||||
msg = str(exc).lower()
|
||||
if (
|
||||
exc.reason == "subscriptionDuplicate"
|
||||
or "already exists" in msg
|
||||
or "duplicate" in msg
|
||||
):
|
||||
# On the reason code, never on the prose: the error no longer carries the body (it is
|
||||
# the request quoted back), and a message YouTube rewords would silently turn "already
|
||||
# following" back into a 422 the user cannot act on. `reason` is comma-joined when the
|
||||
# body listed several.
|
||||
if "subscriptionDuplicate" in exc.reason.split(","):
|
||||
yt_sub_id = ""
|
||||
else:
|
||||
raise HTTPException(
|
||||
|
||||
@@ -405,13 +405,31 @@ def item_detail(
|
||||
"text": codec not in subtitles.BITMAP_SUBS,
|
||||
}
|
||||
)
|
||||
except Exception: # noqa: BLE001 — playback works without the extras; never 500 here
|
||||
# Best-effort, but not free: what this drops is the subtitle stream list and the intro
|
||||
# markers, so the player offers no subtitles and no skip button — and looks like a file
|
||||
# that has neither.
|
||||
# Best-effort, but not free: what either branch drops is the subtitle stream list and the intro
|
||||
# markers, so the player offers no subtitles and no skip button — and looks like a file that has
|
||||
# neither.
|
||||
except PlexNotConfigured:
|
||||
# silent-ok: not a failure but a state — no Plex server, and nothing to play from anyway.
|
||||
pass
|
||||
except PlexError as exc:
|
||||
log.warning(
|
||||
"the live extras for an item could not be fetched",
|
||||
extra=logs.event("plex.detail.extras_failed", rating_key=it.rating_key, kind=it.kind),
|
||||
extra=logs.event(
|
||||
"plex.detail.extras_failed",
|
||||
rating_key=it.rating_key,
|
||||
kind=it.kind,
|
||||
reason=type(exc).__name__,
|
||||
),
|
||||
)
|
||||
except Exception: # noqa: BLE001 — playback works without the extras; never 500 here
|
||||
# Plex answered and WE could not read it — a shape our parsing does not expect. The same
|
||||
# split `show_detail` makes: the stack is the only thing that says where.
|
||||
log.warning(
|
||||
"the live extras for an item could not be read",
|
||||
extra=logs.event(
|
||||
"plex.detail.extras_unreadable", rating_key=it.rating_key, kind=it.kind
|
||||
),
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
# Lazy enrichment (movies only): the live fetch above carries the FULL cast + rich detail (the sync
|
||||
|
||||
@@ -317,8 +317,8 @@ def search_youtube(
|
||||
# themselves, and `redact_text` does not cover it — it scrubs addresses and
|
||||
# credential params, not what someone typed into the search box. What the
|
||||
# ERROR line carries instead is the shape of the failure, which is what tells
|
||||
# an ingest bug from an upstream one; the term follows at TRACE, where the
|
||||
# consent that allows it is the only thing that emits the record at all.
|
||||
# an ingest bug from an upstream one; the term follows at TRACE, and only
|
||||
# under this user's own consent — not whenever the process runs at TRACE.
|
||||
log.exception(
|
||||
"live search ingest failed",
|
||||
extra=logs.event(
|
||||
@@ -328,12 +328,13 @@ def search_youtube(
|
||||
collected=len(collected),
|
||||
),
|
||||
)
|
||||
log.log(
|
||||
logs.TRACE,
|
||||
"the term the ingest failed on",
|
||||
# log-pii-ok: TRACE is the consented level, and this is what it is for
|
||||
extra=logs.event("search.live.term", term=term),
|
||||
)
|
||||
if logs.consented_trace():
|
||||
log.log(
|
||||
logs.TRACE,
|
||||
"the term the ingest failed on",
|
||||
# log-pii-ok: only under this user's own Teljes consent
|
||||
extra=logs.event("search.live.term", term=term),
|
||||
)
|
||||
if collected:
|
||||
break # keep what earlier pages already gathered
|
||||
raise HTTPException(
|
||||
|
||||
Reference in New Issue
Block a user