From f635d6c2330ad4e0cf2ce743092536acbf6f97ee Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Sat, 17 May 2025 21:06:51 +0200 Subject: [PATCH] chore: create ID per request for logging and error response --- package-lock.json | 14 ++++++++++++++ package.json | 1 + src/controller/StreamController.ts | 11 ++++++++--- src/extractor/ExtractorRegistry.test.ts | 2 +- src/extractor/ExtractorRegistry.ts | 4 ++-- src/handler/CineHDPlus.test.ts | 2 +- src/handler/Eurostreaming.test.ts | 2 +- src/handler/Frembed.test.ts | 2 +- src/handler/FrenchCloud.test.ts | 2 +- src/handler/KinoKiste.test.ts | 2 +- src/handler/MeineCloud.test.ts | 2 +- src/handler/MostraGuarda.test.ts | 2 +- src/handler/VerHdLink.test.ts | 2 +- src/index.ts | 6 ++++-- src/types.ts | 5 ++++- src/utils/Fetcher.test.ts | 2 +- src/utils/Fetcher.ts | 4 ++-- src/utils/StreamResolver.test.ts | 4 ++-- src/utils/StreamResolver.ts | 8 ++++---- 19 files changed, 51 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index c81bd45..b55bb71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "slugify": "^1.6.6", "unpacker": "^1.0.1", "user-agents": "^1.1.536", + "uuid": "^11.1.0", "winston": "^3.17.0" }, "devDependencies": { @@ -7096,6 +7097,19 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index 96fd498..b23af41 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "slugify": "^1.6.6", "unpacker": "^1.0.1", "user-agents": "^1.1.536", + "uuid": "^11.1.0", "winston": "^3.17.0" }, "devDependencies": { diff --git a/src/controller/StreamController.ts b/src/controller/StreamController.ts index 04b8b1b..05126eb 100644 --- a/src/controller/StreamController.ts +++ b/src/controller/StreamController.ts @@ -1,7 +1,7 @@ import { Request, Response, Router } from 'express'; import winston from 'winston'; import { Handler } from '../handler'; -import { Config } from '../types'; +import { Config, Context } from '../types'; import { StreamResolver } from '../utils'; export class StreamController { @@ -26,11 +26,16 @@ export class StreamController { const type: string = req.params['type'] || ''; const id: string = req.params['id'] || ''; - this.logger.info(`Search stream for type "${type}" and id "${id}"`); + const ctx: Context = { + id: res.getHeader('X-Request-ID') as string, + ip: req.ip as string, + }; + + this.logger.info(`Search stream for type "${type}" and id "${id}"`, ctx); const selectedHandlers = this.handlers.filter(handler => handler.id in config); - const streams = await this.streamResolver.resolve({ ip: (req.ip as string) }, selectedHandlers, type, id); + const streams = await this.streamResolver.resolve(ctx, selectedHandlers, type, id); res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify({ streams })); diff --git a/src/extractor/ExtractorRegistry.test.ts b/src/extractor/ExtractorRegistry.test.ts index 1e43123..a0f5a4f 100644 --- a/src/extractor/ExtractorRegistry.test.ts +++ b/src/extractor/ExtractorRegistry.test.ts @@ -10,7 +10,7 @@ const fetcher = new Fetcher(); const extractorRegistry = new ExtractorRegistry(logger, fetcher); describe('ExtractorRegistry', () => { - const ctx: Context = { ip: '127.0.0.1' }; + const ctx: Context = { id: 'id', ip: '127.0.0.1' }; test('returns undefined when no extractor can be found', async () => { const urlResult = await extractorRegistry.handle(ctx, new URL('https://some-url.test'), 'en'); diff --git a/src/extractor/ExtractorRegistry.ts b/src/extractor/ExtractorRegistry.ts index 4ec70e5..5e0d822 100644 --- a/src/extractor/ExtractorRegistry.ts +++ b/src/extractor/ExtractorRegistry.ts @@ -33,12 +33,12 @@ export class ExtractorRegistry { return undefined; } - this.logger.info(`Extract stream URL using ${extractor.id} extractor from ${url}`); + this.logger.info(`Extract stream URL using ${extractor.id} extractor from ${url}`, ctx); try { urlResult = await extractor.extract(ctx, url, countryCode); } catch (error) { - this.logger.warn(`${extractor.id} error: ` + error); + this.logger.warn(`${extractor.id} error: ` + error, ctx); return undefined; } diff --git a/src/handler/CineHDPlus.test.ts b/src/handler/CineHDPlus.test.ts index 09f8399..64a3d48 100644 --- a/src/handler/CineHDPlus.test.ts +++ b/src/handler/CineHDPlus.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new CineHDPlus(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1' }; describe('CineHDPlus', () => { test('does not handle non imdb series', async () => { diff --git a/src/handler/Eurostreaming.test.ts b/src/handler/Eurostreaming.test.ts index 4d0703a..a30433b 100644 --- a/src/handler/Eurostreaming.test.ts +++ b/src/handler/Eurostreaming.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new Eurostreaming(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1' }; describe('Eurostreaming', () => { test('does not handle non imdb series', async () => { diff --git a/src/handler/Frembed.test.ts b/src/handler/Frembed.test.ts index 68f1287..e031b36 100644 --- a/src/handler/Frembed.test.ts +++ b/src/handler/Frembed.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new Frembed(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1' }; describe('Frembed', () => { test('does not handle non imdb series', async () => { diff --git a/src/handler/FrenchCloud.test.ts b/src/handler/FrenchCloud.test.ts index b1e8b8a..b029071 100644 --- a/src/handler/FrenchCloud.test.ts +++ b/src/handler/FrenchCloud.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new FrenchCloud(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1' }; describe('FrenchCloud', () => { test('does not handle non imdb movies', async () => { diff --git a/src/handler/KinoKiste.test.ts b/src/handler/KinoKiste.test.ts index 444149b..754d023 100644 --- a/src/handler/KinoKiste.test.ts +++ b/src/handler/KinoKiste.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new KinoKiste(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1' }; describe('KinoKiste', () => { test('does not handle non imdb series', async () => { diff --git a/src/handler/MeineCloud.test.ts b/src/handler/MeineCloud.test.ts index ff65fef..3fad108 100644 --- a/src/handler/MeineCloud.test.ts +++ b/src/handler/MeineCloud.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new MeineCloud(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1' }; describe('MeineCloud', () => { test('does not handle non imdb movies', async () => { diff --git a/src/handler/MostraGuarda.test.ts b/src/handler/MostraGuarda.test.ts index 0b07098..fccfc46 100644 --- a/src/handler/MostraGuarda.test.ts +++ b/src/handler/MostraGuarda.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new MostraGuarda(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1' }; describe('MostraGuarda', () => { test('does not handle non imdb movies', async () => { diff --git a/src/handler/VerHdLink.test.ts b/src/handler/VerHdLink.test.ts index 693defa..31145f2 100644 --- a/src/handler/VerHdLink.test.ts +++ b/src/handler/VerHdLink.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new VerHdLink(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1' }; describe('VerHdLink', () => { test('does not handle non imdb movies', async () => { diff --git a/src/index.ts b/src/index.ts index 17a7c21..f28c3ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import express, { NextFunction, Request, Response } from 'express'; +import { v4 as uuidv4 } from 'uuid'; import axios, { AxiosError } from 'axios'; import axiosRetry, { isNetworkOrIdempotentRequestError } from 'axios-retry'; import { setupCache } from 'axios-cache-interceptor'; @@ -24,8 +25,7 @@ const logger = winston.createLogger({ format: winston.format.combine( winston.format.cli(), winston.format.timestamp(), - winston.format.printf(({ level, message, timestamp }) => `${timestamp} ${level}: ${message}`), - ), + winston.format.printf(({ level, message, timestamp, id }) => `${timestamp} ${level} ${id}: ${message}`)), }), ], }); @@ -60,6 +60,8 @@ const addon = express(); addon.set('trust proxy', true); addon.use((_req: Request, res: Response, next: NextFunction) => { + res.setHeader('X-Request-ID', uuidv4()); + res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Headers', '*'); diff --git a/src/types.ts b/src/types.ts index e8ac694..106f932 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,9 @@ import { Manifest, ManifestConfig } from 'stremio-addon-sdk'; -export interface Context { ip: string } +export interface Context { + id: string; + ip: string; +} export type ManifestWithConfig = Manifest & { config: ManifestConfig[] }; diff --git a/src/utils/Fetcher.test.ts b/src/utils/Fetcher.test.ts index 05235cf..00dda1d 100644 --- a/src/utils/Fetcher.test.ts +++ b/src/utils/Fetcher.test.ts @@ -9,7 +9,7 @@ const axiosMock = new AxiosMockAdapter(axios); const fetcher = new Fetcher(axios, winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] })); describe('fetch', () => { - const ctx: Context = { ip: '127.0.0.1' }; + const ctx: Context = { id: 'id', ip: '127.0.0.1' }; test('text passes successful response through setting headers', async () => { axiosMock.onGet().reply(200, 'some text'); diff --git a/src/utils/Fetcher.ts b/src/utils/Fetcher.ts index d07b3f7..5c3dbb6 100644 --- a/src/utils/Fetcher.ts +++ b/src/utils/Fetcher.ts @@ -16,7 +16,7 @@ export class Fetcher { } readonly text = async (ctx: Context, url: URL, config?: AxiosRequestConfig): Promise => { - this.logger.info(`Fetch ${url}`); + this.logger.info(`Fetch ${url}`, ctx); const response = await this.axios.get(url.href, this.getConfig(ctx, url, config)); @@ -24,7 +24,7 @@ export class Fetcher { }; readonly textPost = async (ctx: Context, url: URL, data: unknown, config?: AxiosRequestConfig): Promise => { - this.logger.info(`Fetch post ${url}`); + this.logger.info(`Fetch post ${url}`, ctx); const response = await this.axios.post(url.href, data, this.getConfig(ctx, url, config)); diff --git a/src/utils/StreamResolver.test.ts b/src/utils/StreamResolver.test.ts index 046dd32..35fac12 100644 --- a/src/utils/StreamResolver.test.ts +++ b/src/utils/StreamResolver.test.ts @@ -8,7 +8,7 @@ jest.mock('../utils/Fetcher'); const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] }); const streamResolver = new StreamResolver(logger); -const ctx: Context = { ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1' }; // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); @@ -34,7 +34,7 @@ describe('resolve', () => { expect(streams).toStrictEqual([{ name: 'WebStreamr', - title: '❌ Error with handler "meinecloud". Please check the logs or create an issue if this persists.', + title: '❌ Error with handler "meinecloud". Please create an issue if this persists. Request-id: id', ytId: 'E4WlUXrJgy4', }]); }); diff --git a/src/utils/StreamResolver.ts b/src/utils/StreamResolver.ts index 72ea97e..37d3d88 100644 --- a/src/utils/StreamResolver.ts +++ b/src/utils/StreamResolver.ts @@ -31,17 +31,17 @@ export class StreamResolver { try { const handlerUrlResults = await handler.handle(ctx, type, id); - this.logger.info(`${handler.id} returned ${handlerUrlResults.length} urls`); + this.logger.info(`${handler.id} returned ${handlerUrlResults.length} urls`, ctx); urlResults.push(...(handlerUrlResults.filter(handlerUrlResult => handlerUrlResult !== undefined))); } catch (err) { streams.push({ name: 'WebStreamr', - title: `❌ Error with handler "${handler.id}". Please check the logs or create an issue if this persists.`, + title: `❌ Error with handler "${handler.id}". Please create an issue if this persists. Request-id: ${ctx.id}`, ytId: 'E4WlUXrJgy4', }); - this.logger.error(`${handler.id} error: ${err}`); + this.logger.error(`${handler.id} error: ${err}`, ctx); } }); await Promise.all(handlerPromises); @@ -60,7 +60,7 @@ export class StreamResolver { return a.label.localeCompare(b.label); }); - this.logger.info(`Return ${urlResults.length} streams`); + this.logger.info(`Return ${urlResults.length} streams`, ctx); streams.push( ...urlResults.map((urlResult) => {