From 6b19f5b54b3250f5abae94547d0b76e0d67269cb Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 23 Feb 2025 15:12:59 +0100 Subject: [PATCH 1/2] feat: debugmode full request --- debug-logs/67b8a2ccd785e367b3c5e37b.json | 31 +++ debug-logs/julienfroidefond@gmail.com.json | 23 +++ src/app/api/debug/route.ts | 30 +++ src/app/books/[bookId]/page.tsx | 5 +- src/app/downloads/page.tsx | 5 +- src/app/layout.tsx | 7 +- src/app/libraries/[libraryId]/page.tsx | 5 +- src/app/libraries/page.tsx | 22 +++ src/app/page.tsx | 5 +- src/app/series/[seriesId]/page.tsx | 5 +- src/components/debug/DebugInfo.tsx | 209 +++++++++++++++++++++ src/components/debug/DebugWrapper.tsx | 14 ++ src/lib/hoc/withPageTiming.tsx | 16 ++ src/lib/services/base-api.service.ts | 71 ++++++- src/lib/services/config-db.service.ts | 116 +++++------- src/lib/services/debug.service.ts | 181 ++++++++++++++++++ src/lib/services/favorite.service.ts | 89 +++++---- src/lib/services/preferences.service.ts | 20 +- 18 files changed, 721 insertions(+), 133 deletions(-) create mode 100644 debug-logs/67b8a2ccd785e367b3c5e37b.json create mode 100644 debug-logs/julienfroidefond@gmail.com.json create mode 100644 src/app/api/debug/route.ts create mode 100644 src/app/libraries/page.tsx create mode 100644 src/components/debug/DebugInfo.tsx create mode 100644 src/components/debug/DebugWrapper.tsx create mode 100644 src/lib/hoc/withPageTiming.tsx create mode 100644 src/lib/services/debug.service.ts diff --git a/debug-logs/67b8a2ccd785e367b3c5e37b.json b/debug-logs/67b8a2ccd785e367b3c5e37b.json new file mode 100644 index 0000000..44da55e --- /dev/null +++ b/debug-logs/67b8a2ccd785e367b3c5e37b.json @@ -0,0 +1,31 @@ +[ + { + "url": "books/ondeck", + "startTime": 2885483.439666003, + "endTime": 2886906.0345830023, + "fromCache": false, + "duration": 1422.59491699934, + "timestamp": "2025-02-23T14:10:34.889Z" + }, + { + "url": "home-on-deck", + "startTime": 2885483.3969579935, + "endTime": 2886915.6758749783, + "fromCache": true, + "cacheType": "HOME", + "duration": 1432.2789169847965, + "timestamp": "2025-02-23T14:10:34.901Z" + }, + { + "url": "Page Render: HomePage", + "startTime": 2885485.3594990075, + "endTime": 2886935.9214159846, + "duration": 1450.5619169771671, + "timestamp": "2025-02-23T14:10:34.912Z", + "fromCache": false, + "pageRender": { + "page": "HomePage", + "duration": 1450.5619169771671 + } + } +] \ No newline at end of file diff --git a/debug-logs/julienfroidefond@gmail.com.json b/debug-logs/julienfroidefond@gmail.com.json new file mode 100644 index 0000000..f824c1d --- /dev/null +++ b/debug-logs/julienfroidefond@gmail.com.json @@ -0,0 +1,23 @@ +[ + { + "url": "series/list", + "startTime": 1555818.8649999797, + "endTime": 1556150.4556659758, + "duration": 331.5906659960747, + "timestamp": "2025-02-23T12:27:42.757Z" + }, + { + "url": "libraries", + "startTime": 1556210.246457994, + "endTime": 1556425.7659159899, + "duration": 215.51945799589157, + "timestamp": "2025-02-23T12:27:43.141Z" + }, + { + "url": "series/0GNTC1Q53EM9R", + "startTime": 1590829.9988330007, + "endTime": 1591134.537375003, + "duration": 304.5385420024395, + "timestamp": "2025-02-23T12:28:17.719Z" + } +] \ No newline at end of file diff --git a/src/app/api/debug/route.ts b/src/app/api/debug/route.ts new file mode 100644 index 0000000..a88daec --- /dev/null +++ b/src/app/api/debug/route.ts @@ -0,0 +1,30 @@ +import { NextRequest, NextResponse } from "next/server"; +import { DebugService } from "@/lib/services/debug.service"; + +export async function GET() { + try { + const logs = await DebugService.getRequestLogs(); + return NextResponse.json(logs); + } catch (error) { + return NextResponse.json({ error: "Erreur lors de la récupération des logs" }, { status: 500 }); + } +} + +export async function POST(request: NextRequest) { + try { + const timing = await request.json(); + await DebugService.logRequest(timing); + return NextResponse.json({ success: true }); + } catch (error) { + return NextResponse.json({ error: "Erreur lors de l'enregistrement du log" }, { status: 500 }); + } +} + +export async function DELETE() { + try { + await DebugService.clearLogs(); + return NextResponse.json({ success: true }); + } catch (error) { + return NextResponse.json({ error: "Erreur lors de la suppression des logs" }, { status: 500 }); + } +} diff --git a/src/app/books/[bookId]/page.tsx b/src/app/books/[bookId]/page.tsx index f9f103d..0e20446 100644 --- a/src/app/books/[bookId]/page.tsx +++ b/src/app/books/[bookId]/page.tsx @@ -3,8 +3,9 @@ import { ClientBookWrapper } from "@/components/reader/ClientBookWrapper"; import { BookSkeleton } from "@/components/skeletons/BookSkeleton"; import { BookService } from "@/lib/services/book.service"; import { notFound } from "next/navigation"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; -export default async function BookPage({ params }: { params: { bookId: string } }) { +async function BookPage({ params }: { params: { bookId: string } }) { try { const data = await BookService.getBook(params.bookId); @@ -18,3 +19,5 @@ export default async function BookPage({ params }: { params: { bookId: string } notFound(); } } + +export default withPageTiming("BookPage", BookPage); diff --git a/src/app/downloads/page.tsx b/src/app/downloads/page.tsx index 71dc642..5fdfcc0 100644 --- a/src/app/downloads/page.tsx +++ b/src/app/downloads/page.tsx @@ -1,7 +1,8 @@ import { PageHeader } from "@/components/layout/PageHeader"; import { DownloadManager } from "@/components/downloads/DownloadManager"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; -export default function DownloadsPage() { +function DownloadsPage() { return ( <> @@ -9,3 +10,5 @@ export default function DownloadsPage() { ); } + +export default withPageTiming("DownloadsPage", DownloadsPage); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 40ef9be..7b7c7ab 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,8 @@ import { Inter } from "next/font/google"; import "@/styles/globals.css"; import { cn } from "@/lib/utils"; import ClientLayout from "@/components/layout/ClientLayout"; +import { PreferencesProvider } from "@/contexts/PreferencesContext"; +import { DebugWrapper } from "@/components/debug/DebugWrapper"; const inter = Inter({ subsets: ["latin"] }); @@ -113,7 +115,10 @@ export default function RootLayout({ children }: { children: React.ReactNode }) /> - {children} + + {children} + + ); diff --git a/src/app/libraries/[libraryId]/page.tsx b/src/app/libraries/[libraryId]/page.tsx index df7fac2..84ac29a 100644 --- a/src/app/libraries/[libraryId]/page.tsx +++ b/src/app/libraries/[libraryId]/page.tsx @@ -3,6 +3,7 @@ import { LibraryService } from "@/lib/services/library.service"; import { PreferencesService } from "@/lib/services/preferences.service"; import { revalidatePath } from "next/cache"; import { RefreshButton } from "@/components/library/RefreshButton"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; interface PageProps { params: { libraryId: string }; @@ -49,7 +50,7 @@ async function getLibrarySeries( } } -export default async function LibraryPage({ params, searchParams }: PageProps) { +async function LibraryPage({ params, searchParams }: PageProps) { const currentPage = searchParams.page ? parseInt(searchParams.page) : 1; const preferences = await PreferencesService.getPreferences(); @@ -105,3 +106,5 @@ export default async function LibraryPage({ params, searchParams }: PageProps) { ); } } + +export default withPageTiming("LibraryPage", LibraryPage); diff --git a/src/app/libraries/page.tsx b/src/app/libraries/page.tsx new file mode 100644 index 0000000..3e1c005 --- /dev/null +++ b/src/app/libraries/page.tsx @@ -0,0 +1,22 @@ +import { LibrariesContent } from "@/components/libraries/LibrariesContent"; +import { LibraryService } from "@/lib/services/library.service"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; + +async function LibrariesPage() { + try { + const libraries = await LibraryService.getLibraries(); + return ; + } catch (error) { + return ( +
+
+

+ {error instanceof Error ? error.message : "Une erreur est survenue"} +

+
+
+ ); + } +} + +export default withPageTiming("LibrariesPage", LibrariesPage); diff --git a/src/app/page.tsx b/src/app/page.tsx index d751675..3a56703 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,6 +2,7 @@ import { HomeContent } from "@/components/home/HomeContent"; import { HomeService } from "@/lib/services/home.service"; import { redirect } from "next/navigation"; import { revalidatePath } from "next/cache"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; async function refreshHome() { "use server"; @@ -16,7 +17,7 @@ async function refreshHome() { } } -export default async function HomePage() { +async function HomePage() { try { const data = await HomeService.getHomeData(); @@ -38,3 +39,5 @@ export default async function HomePage() { ); } } + +export default withPageTiming("HomePage", HomePage); diff --git a/src/app/series/[seriesId]/page.tsx b/src/app/series/[seriesId]/page.tsx index db8909b..72d4800 100644 --- a/src/app/series/[seriesId]/page.tsx +++ b/src/app/series/[seriesId]/page.tsx @@ -3,6 +3,7 @@ import { SeriesHeader } from "@/components/series/SeriesHeader"; import { SeriesService } from "@/lib/services/series.service"; import { PreferencesService } from "@/lib/services/preferences.service"; import { revalidatePath } from "next/cache"; +import { withPageTiming } from "@/lib/hoc/withPageTiming"; interface PageProps { params: { seriesId: string }; @@ -38,7 +39,7 @@ async function refreshSeries(seriesId: string) { } } -export default async function SeriesPage({ params, searchParams }: PageProps) { +async function SeriesPage({ params, searchParams }: PageProps) { const currentPage = searchParams.page ? parseInt(searchParams.page) : 1; const preferences = await PreferencesService.getPreferences(); @@ -76,3 +77,5 @@ export default async function SeriesPage({ params, searchParams }: PageProps) { ); } } + +export default withPageTiming("SeriesPage", SeriesPage); diff --git a/src/components/debug/DebugInfo.tsx b/src/components/debug/DebugInfo.tsx new file mode 100644 index 0000000..796b682 --- /dev/null +++ b/src/components/debug/DebugInfo.tsx @@ -0,0 +1,209 @@ +"use client"; + +import { usePreferences } from "@/contexts/PreferencesContext"; +import { useEffect, useState } from "react"; +import { + X, + Database, + Minimize2, + Maximize2, + Clock, + CircleDot, + Layout, + RefreshCw, + Globe, +} from "lucide-react"; +import { CacheType } from "@/lib/services/base-api.service"; + +interface RequestTiming { + url: string; + startTime: number; + endTime: number; + duration: number; + timestamp: string; + fromCache: boolean; + cacheType?: CacheType; + mongoAccess?: { + operation: string; + duration: number; + }; + pageRender?: { + page: string; + }; +} + +function formatTime(timestamp: string) { + const date = new Date(timestamp); + return date.toLocaleTimeString("fr-FR", { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); +} + +function formatDuration(duration: number) { + return Math.round(duration); +} + +export function DebugInfo() { + const { preferences } = usePreferences(); + const [logs, setLogs] = useState([]); + const [isMinimized, setIsMinimized] = useState(false); + const [isRefreshing, setIsRefreshing] = useState(false); + + const fetchLogs = async () => { + try { + setIsRefreshing(true); + const response = await fetch("/api/debug"); + if (response.ok) { + const data = await response.json(); + setLogs(data); + } + } catch (error) { + console.error("Erreur lors de la récupération des logs:", error); + } finally { + setIsRefreshing(false); + } + }; + + useEffect(() => { + fetchLogs(); + }, []); + + const clearLogs = async () => { + try { + await fetch("/api/debug", { method: "DELETE" }); + setLogs([]); + } catch (error) { + console.error("Erreur lors de la suppression des logs:", error); + } + }; + + const sortedLogs = [...logs].reverse(); + + return ( +
+
+

DEBUG !

+
+ + + +
+
+ + {!isMinimized && ( +
+ {sortedLogs.length === 0 ? ( +

Aucune requête enregistrée

+ ) : ( + sortedLogs.map((log, index) => ( +
+
+
+ {log.fromCache && ( +
+ +
+ )} + {log.mongoAccess && ( +
+ +
+ )} + {log.pageRender && ( +
+ +
+ )} + {!log.fromCache && !log.mongoAccess && !log.pageRender && ( +
+ +
+ )} + + {log.url} + +
+
+
+ + {formatTime(log.timestamp)} +
+ + {formatDuration(log.duration)}ms + + {log.mongoAccess && ( + + +{formatDuration(log.mongoAccess.duration)}ms + + )} +
+
+
+
+ {log.mongoAccess && ( +
+ )} +
+
+ )) + )} +
+ )} +
+ ); +} diff --git a/src/components/debug/DebugWrapper.tsx b/src/components/debug/DebugWrapper.tsx new file mode 100644 index 0000000..91120ad --- /dev/null +++ b/src/components/debug/DebugWrapper.tsx @@ -0,0 +1,14 @@ +"use client"; + +import { usePreferences } from "@/contexts/PreferencesContext"; +import { DebugInfo } from "./DebugInfo"; + +export function DebugWrapper() { + const { preferences } = usePreferences(); + console.log(preferences); + if (!preferences.debug) { + return null; + } + + return ; +} diff --git a/src/lib/hoc/withPageTiming.tsx b/src/lib/hoc/withPageTiming.tsx new file mode 100644 index 0000000..c9a4d68 --- /dev/null +++ b/src/lib/hoc/withPageTiming.tsx @@ -0,0 +1,16 @@ +import { DebugService } from "@/lib/services/debug.service"; + +type PageComponent = (props: any) => Promise | JSX.Element; + +export function withPageTiming(pageName: string, Component: PageComponent) { + return async function PageWithTiming(props: any) { + const start = performance.now(); + const result = await Promise.resolve(Component(props)); + const duration = performance.now() - start; + + // Log le temps de rendu + await DebugService.logPageRender(pageName, duration); + + return result; + }; +} diff --git a/src/lib/services/base-api.service.ts b/src/lib/services/base-api.service.ts index 29e0bc3..fddb58e 100644 --- a/src/lib/services/base-api.service.ts +++ b/src/lib/services/base-api.service.ts @@ -1,6 +1,7 @@ import { AuthConfig } from "@/types/auth"; import { serverCacheService } from "./server-cache.service"; import { ConfigDBService } from "./config-db.service"; +import { DebugService } from "./debug.service"; // Types de cache disponibles export type CacheType = "DEFAULT" | "HOME" | "LIBRARIES" | "SERIES" | "BOOKS" | "IMAGES"; @@ -51,7 +52,36 @@ export abstract class BaseApiService { fetcher: () => Promise, type: CacheType = "DEFAULT" ): Promise { - return serverCacheService.getOrSet(key, fetcher, type); + const startTime = performance.now(); + + try { + const result = await serverCacheService.getOrSet(key, fetcher, type); + const endTime = performance.now(); + + // Log la requête avec l'indication du cache + await DebugService.logRequest({ + url: key, + startTime, + endTime, + fromCache: true, + cacheType: type, + }); + + return result; + } catch (error) { + const endTime = performance.now(); + + // Log aussi les erreurs + await DebugService.logRequest({ + url: key, + startTime, + endTime, + fromCache: true, + cacheType: type, + }); + + throw error; + } } protected static handleError(error: unknown, defaultMessage: string): never { @@ -87,6 +117,7 @@ export abstract class BaseApiService { headersOptions = {}, options: KomgaRequestInit = {} ): Promise { + const startTime = performance.now(); const config = await this.getKomgaConfig(); const { path, params } = urlBuilder; const url = this.buildUrl(config, path, params); @@ -97,16 +128,36 @@ export abstract class BaseApiService { headers.set(key as string, value as string); } } - // console.log("🛜 Fetching from", url); - // console.log("Headers", headers); - // console.log("headersOptions", headersOptions); - // console.log("options", options); - const response = await fetch(url, { headers, ...options }); - if (!response.ok) { - throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`); + try { + const response = await fetch(url, { headers, ...options }); + const endTime = performance.now(); + + // Log la requête + await DebugService.logRequest({ + url: path, + startTime, + endTime, + fromCache: false, + }); + + if (!response.ok) { + throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`); + } + + return options.isImage ? response : response.json(); + } catch (error) { + const endTime = performance.now(); + + // Log aussi les erreurs + await DebugService.logRequest({ + url: path, + startTime, + endTime, + fromCache: false, + }); + + throw error; } - - return options.isImage ? response : response.json(); } } diff --git a/src/lib/services/config-db.service.ts b/src/lib/services/config-db.service.ts index cb66977..d3b1cb7 100644 --- a/src/lib/services/config-db.service.ts +++ b/src/lib/services/config-db.service.ts @@ -2,6 +2,8 @@ import { cookies } from "next/headers"; import connectDB from "@/lib/mongodb"; import { KomgaConfig } from "@/lib/models/config.model"; import { TTLConfig } from "@/lib/models/ttl-config.model"; +import { DebugService } from "./debug.service"; +import { AuthServerService } from "./auth-server.service"; interface User { id: string; @@ -24,93 +26,67 @@ interface TTLConfigData { } export class ConfigDBService { - private static async getCurrentUser(): Promise { - const userCookie = cookies().get("stripUser"); - - if (!userCookie) { + private static getCurrentUser(): User { + const user = AuthServerService.getCurrentUser(); + if (!user) { throw new Error("Utilisateur non authentifié"); } - - try { - return JSON.parse(atob(userCookie.value)); - } catch (error) { - console.error("Erreur lors de la récupération de l'utilisateur depuis le cookie:", error); - throw new Error("Utilisateur non authentifié"); - } - } - - static async saveConfig(data: KomgaConfigData) { - const user = await this.getCurrentUser(); - await connectDB(); - - const config = await KomgaConfig.findOneAndUpdate( - { userId: user.id }, - { - userId: user.id, - url: data.url, - username: data.username, - password: data.password, - }, - { upsert: true, new: true } - ); - - return config; + return user; } static async getConfig() { - const user = await this.getCurrentUser(); + const user = this.getCurrentUser(); await connectDB(); - const config = await KomgaConfig.findOne({ userId: user.id }); - - if (!config) { - throw new Error("Configuration non trouvée"); - } - - return config; + return DebugService.measureMongoOperation("getConfig", async () => { + const config = await KomgaConfig.findOne({ userId: user.id }); + return config; + }); } - static async saveTTLConfig(data: TTLConfigData) { - const user = await this.getCurrentUser(); + static async saveConfig(data: KomgaConfigData) { + const user = this.getCurrentUser(); await connectDB(); - const config = await TTLConfig.findOneAndUpdate( - { userId: user.id }, - { - userId: user.id, - ...data, - }, - { upsert: true, new: true } - ); - - return config; + return DebugService.measureMongoOperation("saveConfig", async () => { + const config = await KomgaConfig.findOneAndUpdate( + { userId: user.id }, + { + userId: user.id, + url: data.url, + username: data.username, + password: data.password, + }, + { upsert: true, new: true } + ); + return config; + }); } static async getTTLConfig() { - const user = await this.getCurrentUser(); + const user = this.getCurrentUser(); await connectDB(); - const config = await TTLConfig.findOne({ userId: user.id }); + return DebugService.measureMongoOperation("getTTLConfig", async () => { + const config = await TTLConfig.findOne({ userId: user.id }); + return config; + }); + } - if (!config) { - // Retourner la configuration par défaut si aucune configuration n'existe - return { - defaultTTL: 5, - homeTTL: 5, - librariesTTL: 1440, - seriesTTL: 5, - booksTTL: 5, - imagesTTL: 1440, - }; - } + static async saveTTLConfig(data: TTLConfigData) { + const user = this.getCurrentUser(); + await connectDB(); - return { - defaultTTL: config.defaultTTL, - homeTTL: config.homeTTL, - librariesTTL: config.librariesTTL, - seriesTTL: config.seriesTTL, - booksTTL: config.booksTTL, - imagesTTL: config.imagesTTL, - }; + return DebugService.measureMongoOperation("saveTTLConfig", async () => { + const config = await TTLConfig.findOneAndUpdate( + { userId: user.id }, + { + userId: user.id, + ...data, + }, + { upsert: true, new: true } + ); + return config; + }); } } diff --git a/src/lib/services/debug.service.ts b/src/lib/services/debug.service.ts new file mode 100644 index 0000000..d553963 --- /dev/null +++ b/src/lib/services/debug.service.ts @@ -0,0 +1,181 @@ +import fs from "fs/promises"; +import path from "path"; +import { CacheType } from "./base-api.service"; +import { AuthServerService } from "./auth-server.service"; +import { PreferencesService } from "./preferences.service"; + +interface RequestTiming { + url: string; + startTime: number; + endTime: number; + duration: number; + timestamp: string; + fromCache: boolean; + cacheType?: CacheType; + mongoAccess?: { + operation: string; + duration: number; + }; + pageRender?: { + page: string; + duration: number; + }; +} + +export class DebugService { + private static getCurrentUserId(): string { + const user = AuthServerService.getCurrentUser(); + if (!user) { + throw new Error("Utilisateur non authentifié"); + } + return user.id; + } + + private static getLogFilePath(userId: string): string { + return path.join(process.cwd(), "debug-logs", `${userId}.json`); + } + + private static async ensureDebugDir(): Promise { + const debugDir = path.join(process.cwd(), "debug-logs"); + try { + await fs.access(debugDir); + } catch { + await fs.mkdir(debugDir, { recursive: true }); + } + } + + static async logRequest(timing: Omit) { + try { + const userId = await this.getCurrentUserId(); + const preferences = await PreferencesService.getPreferences(); + if (!preferences.debug) { + return; + } + await this.ensureDebugDir(); + const filePath = this.getLogFilePath(userId); + + let logs: RequestTiming[] = []; + try { + const content = await fs.readFile(filePath, "utf-8"); + logs = JSON.parse(content); + } catch { + // Le fichier n'existe pas encore ou est vide + } + + const newTiming: RequestTiming = { + ...timing, + duration: timing.endTime - timing.startTime, + timestamp: new Date().toISOString(), + }; + + // Garde les 100 dernières requêtes + logs = [...logs.slice(-99), newTiming]; + + await fs.writeFile(filePath, JSON.stringify(logs, null, 2)); + } catch (error) { + // Ignore les erreurs de logging + console.error("Erreur lors de l'enregistrement du log:", error); + } + } + + static async getRequestLogs(): Promise { + try { + const userId = await this.getCurrentUserId(); + const filePath = this.getLogFilePath(userId); + const content = await fs.readFile(filePath, "utf-8"); + return JSON.parse(content); + } catch { + return []; + } + } + + static async clearLogs(): Promise { + try { + const userId = await this.getCurrentUserId(); + const filePath = this.getLogFilePath(userId); + await fs.writeFile(filePath, "[]"); + } catch { + // Ignore les erreurs si le fichier n'existe pas + } + } + + static async logPageRender(page: string, duration: number) { + try { + const userId = await this.getCurrentUserId(); + const preferences = await PreferencesService.getPreferences(); + if (!preferences.debug) { + return; + } + await this.ensureDebugDir(); + const filePath = this.getLogFilePath(userId); + + let logs: RequestTiming[] = []; + try { + const content = await fs.readFile(filePath, "utf-8"); + logs = JSON.parse(content); + } catch { + // Le fichier n'existe pas encore ou est vide + } + + const now = performance.now(); + const newTiming: RequestTiming = { + url: `Page Render: ${page}`, + startTime: now - duration, + endTime: now, + duration, + timestamp: new Date().toISOString(), + fromCache: false, + pageRender: { + page, + duration, + }, + }; + + // Garde les 100 dernières requêtes + logs = [...logs.slice(-99), newTiming]; + + await fs.writeFile(filePath, JSON.stringify(logs, null, 2)); + } catch (error) { + // Ignore les erreurs de logging + console.error("Erreur lors de l'enregistrement du log de page:", error); + } + } + + static async measureMongoOperation(operation: string, func: () => Promise): Promise { + const startTime = performance.now(); + try { + const preferences = await PreferencesService.getPreferences(); + if (!preferences.debug) { + return func(); + } + const result = await func(); + const endTime = performance.now(); + + await this.logRequest({ + url: `MongoDB: ${operation}`, + startTime, + endTime, + fromCache: false, + mongoAccess: { + operation, + duration: endTime - startTime, + }, + }); + + return result; + } catch (error) { + const endTime = performance.now(); + await this.logRequest({ + url: `MongoDB Error: ${operation}`, + startTime, + endTime, + fromCache: false, + mongoAccess: { + operation, + duration: endTime - startTime, + }, + }); + throw error; + } + } +} diff --git a/src/lib/services/favorite.service.ts b/src/lib/services/favorite.service.ts index ae18c64..148a760 100644 --- a/src/lib/services/favorite.service.ts +++ b/src/lib/services/favorite.service.ts @@ -1,6 +1,8 @@ import { cookies } from "next/headers"; import connectDB from "@/lib/mongodb"; import { FavoriteModel } from "@/lib/models/favorite.model"; +import { DebugService } from "./debug.service"; +import { AuthServerService } from "./auth-server.service"; interface User { id: string; @@ -17,19 +19,12 @@ export class FavoriteService { } } - private static async getCurrentUser(): Promise { - const userCookie = cookies().get("stripUser"); - - if (!userCookie) { - throw new Error("Utilisateur non authentifié"); - } - - try { - return JSON.parse(atob(userCookie.value)); - } catch (error) { - console.error("Erreur lors de la récupération de l'utilisateur depuis le cookie:", error); + private static getCurrentUser(): User { + const user = AuthServerService.getCurrentUser(); + if (!user) { throw new Error("Utilisateur non authentifié"); } + return user; } /** @@ -37,15 +32,16 @@ export class FavoriteService { */ static async isFavorite(seriesId: string): Promise { try { - const user = await this.getCurrentUser(); + const user = this.getCurrentUser(); await connectDB(); - const favorite = await FavoriteModel.findOne({ - userId: user.id, - seriesId: seriesId, + return DebugService.measureMongoOperation("isFavorite", async () => { + const favorite = await FavoriteModel.findOne({ + userId: user.id, + seriesId: seriesId, + }); + return !!favorite; }); - - return !!favorite; } catch (error) { console.error("Erreur lors de la vérification du favori:", error); return false; @@ -57,14 +53,16 @@ export class FavoriteService { */ static async addToFavorites(seriesId: string): Promise { try { - const user = await this.getCurrentUser(); + const user = this.getCurrentUser(); await connectDB(); - await FavoriteModel.findOneAndUpdate( - { userId: user.id, seriesId }, - { userId: user.id, seriesId }, - { upsert: true } - ); + await DebugService.measureMongoOperation("addToFavorites", async () => { + await FavoriteModel.findOneAndUpdate( + { userId: user.id, seriesId }, + { userId: user.id, seriesId }, + { upsert: true } + ); + }); this.dispatchFavoritesChanged(); } catch (error) { @@ -78,12 +76,14 @@ export class FavoriteService { */ static async removeFromFavorites(seriesId: string): Promise { try { - const user = await this.getCurrentUser(); + const user = this.getCurrentUser(); await connectDB(); - await FavoriteModel.findOneAndDelete({ - userId: user.id, - seriesId, + await DebugService.measureMongoOperation("removeFromFavorites", async () => { + await FavoriteModel.findOneAndDelete({ + userId: user.id, + seriesId, + }); }); this.dispatchFavoritesChanged(); @@ -97,15 +97,36 @@ export class FavoriteService { * Récupère tous les IDs des séries favorites */ static async getAllFavoriteIds(): Promise { - try { - const user = await this.getCurrentUser(); - await connectDB(); + const user = this.getCurrentUser(); + await connectDB(); + return DebugService.measureMongoOperation("getAllFavoriteIds", async () => { const favorites = await FavoriteModel.find({ userId: user.id }); return favorites.map((favorite) => favorite.seriesId); - } catch (error) { - console.error("Erreur lors de la récupération des favoris:", error); - return []; - } + }); + } + + static async addFavorite(seriesId: string) { + const user = this.getCurrentUser(); + await connectDB(); + + return DebugService.measureMongoOperation("addFavorite", async () => { + const favorite = await FavoriteModel.findOneAndUpdate( + { userId: user.id, seriesId }, + { userId: user.id, seriesId }, + { upsert: true, new: true } + ); + return favorite; + }); + } + + static async removeFavorite(seriesId: string): Promise { + const user = this.getCurrentUser(); + await connectDB(); + + return DebugService.measureMongoOperation("removeFavorite", async () => { + const result = await FavoriteModel.deleteOne({ userId: user.id, seriesId }); + return result.deletedCount > 0; + }); } } diff --git a/src/lib/services/preferences.service.ts b/src/lib/services/preferences.service.ts index 6dc2c44..f3a6329 100644 --- a/src/lib/services/preferences.service.ts +++ b/src/lib/services/preferences.service.ts @@ -1,5 +1,6 @@ import { cookies } from "next/headers"; import { PreferencesModel } from "@/lib/models/preferences.model"; +import { AuthServerService } from "./auth-server.service"; interface User { id: string; @@ -21,24 +22,17 @@ const defaultPreferences: UserPreferences = { }; export class PreferencesService { - static async getCurrentUser(): Promise { - const userCookie = cookies().get("stripUser"); - - if (!userCookie) { - throw new Error("Utilisateur non authentifié"); - } - - try { - return JSON.parse(atob(userCookie.value)); - } catch (error) { - console.error("Erreur lors de la récupération de l'utilisateur depuis le cookie:", error); + static getCurrentUser(): User { + const user = AuthServerService.getCurrentUser(); + if (!user) { throw new Error("Utilisateur non authentifié"); } + return user; } static async getPreferences(): Promise { try { - const user = await this.getCurrentUser(); + const user = this.getCurrentUser(); const preferences = await PreferencesModel.findOne({ userId: user.id }); if (!preferences) { return defaultPreferences; @@ -55,7 +49,7 @@ export class PreferencesService { static async updatePreferences(preferences: Partial): Promise { try { - const user = await this.getCurrentUser(); + const user = this.getCurrentUser(); const updatedPreferences = await PreferencesModel.findOneAndUpdate( { userId: user.id }, { $set: preferences }, From 66e1db7788d6aea83fe6e42ec986334e2b2703fe Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 23 Feb 2025 22:19:24 +0100 Subject: [PATCH 2/2] fix: Better logging and revie system of debug --- .gitignore | 1 + debug-logs/67b8a2ccd785e367b3c5e37b.json | 31 ------------- debug-logs/julienfroidefond@gmail.com.json | 23 ---------- src/app/layout.tsx | 2 - src/components/debug/DebugInfo.tsx | 28 ++++++++++-- src/components/debug/DebugWrapper.tsx | 1 - src/components/layout/ClientLayout.tsx | 2 + src/contexts/PreferencesContext.tsx | 53 ++++++++++++---------- src/lib/hoc/withPageTiming.tsx | 3 +- src/lib/services/base-api.service.ts | 22 --------- src/lib/services/server-cache.service.ts | 14 ++++++ 11 files changed, 70 insertions(+), 110 deletions(-) delete mode 100644 debug-logs/67b8a2ccd785e367b3c5e37b.json delete mode 100644 debug-logs/julienfroidefond@gmail.com.json diff --git a/.gitignore b/.gitignore index cbb590b..a538ba3 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ next-env.d.ts .vscode .cache +debug-logs # Environment variables .env diff --git a/debug-logs/67b8a2ccd785e367b3c5e37b.json b/debug-logs/67b8a2ccd785e367b3c5e37b.json deleted file mode 100644 index 44da55e..0000000 --- a/debug-logs/67b8a2ccd785e367b3c5e37b.json +++ /dev/null @@ -1,31 +0,0 @@ -[ - { - "url": "books/ondeck", - "startTime": 2885483.439666003, - "endTime": 2886906.0345830023, - "fromCache": false, - "duration": 1422.59491699934, - "timestamp": "2025-02-23T14:10:34.889Z" - }, - { - "url": "home-on-deck", - "startTime": 2885483.3969579935, - "endTime": 2886915.6758749783, - "fromCache": true, - "cacheType": "HOME", - "duration": 1432.2789169847965, - "timestamp": "2025-02-23T14:10:34.901Z" - }, - { - "url": "Page Render: HomePage", - "startTime": 2885485.3594990075, - "endTime": 2886935.9214159846, - "duration": 1450.5619169771671, - "timestamp": "2025-02-23T14:10:34.912Z", - "fromCache": false, - "pageRender": { - "page": "HomePage", - "duration": 1450.5619169771671 - } - } -] \ No newline at end of file diff --git a/debug-logs/julienfroidefond@gmail.com.json b/debug-logs/julienfroidefond@gmail.com.json deleted file mode 100644 index f824c1d..0000000 --- a/debug-logs/julienfroidefond@gmail.com.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - { - "url": "series/list", - "startTime": 1555818.8649999797, - "endTime": 1556150.4556659758, - "duration": 331.5906659960747, - "timestamp": "2025-02-23T12:27:42.757Z" - }, - { - "url": "libraries", - "startTime": 1556210.246457994, - "endTime": 1556425.7659159899, - "duration": 215.51945799589157, - "timestamp": "2025-02-23T12:27:43.141Z" - }, - { - "url": "series/0GNTC1Q53EM9R", - "startTime": 1590829.9988330007, - "endTime": 1591134.537375003, - "duration": 304.5385420024395, - "timestamp": "2025-02-23T12:28:17.719Z" - } -] \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7b7c7ab..ee518a4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,7 +4,6 @@ import "@/styles/globals.css"; import { cn } from "@/lib/utils"; import ClientLayout from "@/components/layout/ClientLayout"; import { PreferencesProvider } from "@/contexts/PreferencesContext"; -import { DebugWrapper } from "@/components/debug/DebugWrapper"; const inter = Inter({ subsets: ["latin"] }); @@ -117,7 +116,6 @@ export default function RootLayout({ children }: { children: React.ReactNode }) {children} - diff --git a/src/components/debug/DebugInfo.tsx b/src/components/debug/DebugInfo.tsx index 796b682..d72f629 100644 --- a/src/components/debug/DebugInfo.tsx +++ b/src/components/debug/DebugInfo.tsx @@ -2,6 +2,7 @@ import { usePreferences } from "@/contexts/PreferencesContext"; import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; import { X, Database, @@ -46,10 +47,10 @@ function formatDuration(duration: number) { } export function DebugInfo() { - const { preferences } = usePreferences(); const [logs, setLogs] = useState([]); const [isMinimized, setIsMinimized] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); + const pathname = usePathname(); const fetchLogs = async () => { try { @@ -66,9 +67,21 @@ export function DebugInfo() { } }; + // Rafraîchir les logs au montage et à chaque changement de page useEffect(() => { fetchLogs(); - }, []); + }, [pathname]); + + // Rafraîchir les logs périodiquement si la fenêtre n'est pas minimisée + useEffect(() => { + if (isMinimized) return; + + const interval = setInterval(() => { + fetchLogs(); + }, 5000); // Rafraîchir toutes les 5 secondes + + return () => clearInterval(interval); + }, [isMinimized]); const clearLogs = async () => { try { @@ -83,12 +96,19 @@ export function DebugInfo() { return (
-

DEBUG !

+
+

DEBUG

+ {!isMinimized && ( + + {sortedLogs.length} entrée{sortedLogs.length > 1 ? "s" : ""} + + )} +
diff --git a/src/contexts/PreferencesContext.tsx b/src/contexts/PreferencesContext.tsx index d85ef4c..4b2b378 100644 --- a/src/contexts/PreferencesContext.tsx +++ b/src/contexts/PreferencesContext.tsx @@ -28,25 +28,25 @@ export function PreferencesProvider({ children }: { children: React.ReactNode }) const [preferences, setPreferences] = useState(defaultPreferences); const [isLoading, setIsLoading] = useState(true); - useEffect(() => { - const fetchPreferences = async () => { - try { - const response = await fetch("/api/preferences"); - if (!response.ok) throw new Error("Erreur lors de la récupération des préférences"); - const data = await response.json(); - setPreferences({ - ...defaultPreferences, - ...data, - }); - } catch (error) { - console.error("Erreur lors de la récupération des préférences:", error); - // En cas d'erreur, on garde les préférences par défaut - setPreferences(defaultPreferences); - } finally { - setIsLoading(false); - } - }; + const fetchPreferences = async () => { + try { + const response = await fetch("/api/preferences"); + if (!response.ok) throw new Error("Erreur lors de la récupération des préférences"); + const data = await response.json(); + setPreferences({ + ...defaultPreferences, + ...data, + }); + } catch (error) { + console.error("Erreur lors de la récupération des préférences:", error); + // En cas d'erreur, on garde les préférences par défaut + setPreferences(defaultPreferences); + } finally { + setIsLoading(false); + } + }; + useEffect(() => { fetchPreferences(); }, []); @@ -64,14 +64,17 @@ export function PreferencesProvider({ children }: { children: React.ReactNode }) const updatedPreferences = await response.json(); - setPreferences((prev) => { - const newState = { - ...prev, - ...updatedPreferences, - }; - return newState; - }); + setPreferences((prev) => ({ + ...prev, + ...updatedPreferences, + })); + + // Forcer un rafraîchissement des préférences + await fetchPreferences(); + + return updatedPreferences; } catch (error) { + console.error("Erreur lors de la mise à jour des préférences:", error); throw error; } }; diff --git a/src/lib/hoc/withPageTiming.tsx b/src/lib/hoc/withPageTiming.tsx index c9a4d68..9b80f66 100644 --- a/src/lib/hoc/withPageTiming.tsx +++ b/src/lib/hoc/withPageTiming.tsx @@ -7,9 +7,8 @@ export function withPageTiming(pageName: string, Component: PageComponent) { const start = performance.now(); const result = await Promise.resolve(Component(props)); const duration = performance.now() - start; - // Log le temps de rendu - await DebugService.logPageRender(pageName, duration); + await DebugService.logPageRender(pageName + JSON.stringify(props.params), duration); return result; }; diff --git a/src/lib/services/base-api.service.ts b/src/lib/services/base-api.service.ts index 4be421b..06480d7 100644 --- a/src/lib/services/base-api.service.ts +++ b/src/lib/services/base-api.service.ts @@ -53,34 +53,12 @@ export abstract class BaseApiService { type: CacheType = "DEFAULT" ): Promise { const cacheService = await getServerCacheService(); - const startTime = performance.now(); try { const result = await cacheService.getOrSet(key, fetcher, type); - const endTime = performance.now(); - - // Log la requête avec l'indication du cache - await DebugService.logRequest({ - url: key, - startTime, - endTime, - fromCache: true, - cacheType: type, - }); return result; } catch (error) { - const endTime = performance.now(); - - // Log aussi les erreurs - await DebugService.logRequest({ - url: key, - startTime, - endTime, - fromCache: true, - cacheType: type, - }); - throw error; } } diff --git a/src/lib/services/server-cache.service.ts b/src/lib/services/server-cache.service.ts index de71eed..a764621 100644 --- a/src/lib/services/server-cache.service.ts +++ b/src/lib/services/server-cache.service.ts @@ -1,6 +1,7 @@ import fs from "fs"; import path from "path"; import { PreferencesService } from "./preferences.service"; +import { DebugService } from "./debug.service"; type CacheMode = "file" | "memory"; @@ -370,13 +371,26 @@ class ServerCacheService { fetcher: () => Promise, type: keyof typeof ServerCacheService.DEFAULT_TTL = "DEFAULT" ): Promise { + const startTime = performance.now(); + const cached = this.get(key); if (cached !== null) { + const endTime = performance.now(); + + // Log la requête avec l'indication du cache + await DebugService.logRequest({ + url: key, + startTime, + endTime, + fromCache: true, + cacheType: type, + }); return cached as T; } try { const data = await fetcher(); + this.set(key, data, type); return data; } catch (error) {