From e3cfa0f2dc3f7fc74c90db9f5636e7b4c43eb0c8 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Wed, 9 Apr 2025 13:37:16 -0400 Subject: [PATCH 13/13] metrics: track unidentified innertube errors --- src/lib/helpers/config.ts | 4 ++ src/lib/helpers/metrics.ts | 117 +++++++++++++++++++++++++++++++------ src/main.ts | 4 +- 3 files changed, 106 insertions(+), 19 deletions(-) diff --git a/src/lib/helpers/config.ts b/src/lib/helpers/config.ts index acb04bb..20e5560 100644 --- a/src/lib/helpers/config.ts +++ b/src/lib/helpers/config.ts @@ -23,6 +23,10 @@ export const ConfigSchema = z.object({ disable_logs: z.boolean().default( Deno.env.get("SERVER_DISABLE_LOGS") === "true" || false, ), + track_unknown_innertube_errors: z.boolean().default( + Deno.env.get("SERVER_TRACK_UNKNOWN_INNERTUBE_ERRORS") === "true" || + false, + ), }).strict().default({}), cache: z.object({ enabled: z.boolean().default(true), diff --git a/src/lib/helpers/metrics.ts b/src/lib/helpers/metrics.ts index 5dee540..c22c792 100644 --- a/src/lib/helpers/metrics.ts +++ b/src/lib/helpers/metrics.ts @@ -2,14 +2,24 @@ import { IRawResponse } from "youtubei.js"; import { Counter, Registry } from "prom-client"; export class Metrics { + trackUnknownInnertubeErrors: boolean; private METRICS_PREFIX = "invidious_companion_"; public register = new Registry(); - public createCounter(name: string, help?: string): Counter { + constructor(trackUnknownInnertubeErrors: boolean) { + this.trackUnknownInnertubeErrors = trackUnknownInnertubeErrors; + } + + public createCounter( + name: string, + help?: string, + labels?: string[], + ): Counter { return new Counter({ name: `${this.METRICS_PREFIX}${name}`, help: help || "No help has been provided for this metric", registers: [this.register], + labelNames: Array.isArray(labels) ? labels : [], }); } @@ -26,6 +36,7 @@ export class Metrics { private innertubeErrorStatusUnknown = this.createCounter( "innertube_error_status_unknown_total", "Number of times that an unknown status has been returned by Innertube API", + ["error"], ); private innertubeErrorReasonSignIn = this.createCounter( @@ -66,26 +77,62 @@ export class Metrics { private checkStatus(videoData: IRawResponse) { const status = videoData.playabilityStatus?.status; - return { - unplayable: status === - "UNPLAYABLE", - contentCheckRequired: status === - "CONTENT_CHECK_REQUIRED", - loginRequired: status === "LOGIN_REQUIRED", + interface Error { + unplayable: boolean; + contentCheckRequired: boolean; + loginRequired: boolean; + unknown: string | undefined; + } + + const error: Error = { + unplayable: false, + contentCheckRequired: false, + loginRequired: false, + unknown: undefined, }; + + switch (status) { + case "UNPLAYABLE": + error.unplayable = true; + return error; + case "CONTENT_CHECK_REQUIRED": + error.contentCheckRequired = true; + return error; + case "LOGIN_REQUIRED": + error.loginRequired = true; + return error; + default: + error.unknown = status; + return error; + } } private checkReason(videoData: IRawResponse) { const reason = videoData.playabilityStatus?.reason; - return { - signInToConfirmAge: reason?.includes( - "Sign in to confirm your age", - ), - SignInToConfirmBot: reason?.includes( - "Sign in to confirm you’re not a bot", - ), + interface Error { + signInToConfirmAge: boolean; + SignInToConfirmBot: boolean; + unknown: string | undefined; + } + + const error: Error = { + signInToConfirmAge: false, + SignInToConfirmBot: false, + unknown: undefined, }; + + switch (true) { + case reason?.includes("Sign in to confirm your age"): + error.signInToConfirmAge = true; + return error; + case reason?.includes("Sign in to confirm you’re not a bot"): + error.SignInToConfirmBot = true; + return error; + default: + error.unknown = reason; + return error; + } } private checkSubreason(videoData: IRawResponse) { @@ -93,17 +140,51 @@ export class Metrics { ?.playerErrorMessageRenderer ?.subreason?.runs?.[0]?.text; - return { - thisHelpsProtectCommunity: subReason?.includes( - "This helps protect our community", - ), + interface Error { + thisHelpsProtectCommunity: boolean; + unknown: string | undefined; + } + + const error: Error = { + thisHelpsProtectCommunity: false, + unknown: undefined, }; + + switch (true) { + case subReason?.includes("This helps protect our community"): + error.thisHelpsProtectCommunity = true; + return error; + default: + error.unknown = subReason; + return error; + } } public checkInnertubeResponse(videoData: IRawResponse) { this.innertubeFailedRequest.inc(); const status = this.checkStatus(videoData); + if (this.trackUnknownInnertubeErrors) { + if (status?.unknown) { + this.innertubeErrorStatusUnknown.labels({ + error: status.unknown, + }).inc(); + const reason = this.checkReason(videoData); + if (reason) { + this.innertubeErrorReasonUnknown.labels({ + error: status.unknown, + }).inc(); + const subReason = this.checkSubreason(videoData); + if (subReason) { + this.innertubeErrorStatusUnknown.labels({ + error: status.unknown, + }).inc(); + } + } + return; + } + } + if (status.contentCheckRequired || status.unplayable) return; if (status.loginRequired) { diff --git a/src/main.ts b/src/main.ts index f659d97..84ef286 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,7 +27,9 @@ declare module "hono" { interface ContextVariableMap extends HonoVariables {} } const app = new Hono(); -const metrics = config.server.enable_metrics ? new Metrics() : undefined; +const metrics = config.server.enable_metrics + ? new Metrics(config.server.track_unknown_innertube_errors) + : undefined; let tokenMinter: TokenMinter; let innertubeClient: Innertube; -- 2.49.0