From 9ee441773c30d8b546f2deb22fb596bbfe6a421f Mon Sep 17 00:00:00 2001 From: alma Date: Wed, 7 Jan 2026 10:49:57 +0100 Subject: [PATCH] Refactor Logging BIG --- app/api/leantime/tasks/route.ts | 94 ++++++++++++++++----------- app/api/news/route.ts | 48 ++++++++++---- app/api/rocket-chat/messages/route.ts | 77 +++++++++++++--------- lib/services/microsoft-oauth.ts | 9 +-- 4 files changed, 139 insertions(+), 89 deletions(-) diff --git a/app/api/leantime/tasks/route.ts b/app/api/leantime/tasks/route.ts index 1257322c..c2e8c908 100644 --- a/app/api/leantime/tasks/route.ts +++ b/app/api/leantime/tasks/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/options"; import { getCachedTasksData, cacheTasksData } from "@/lib/redis"; +import { logger } from "@/lib/logger"; interface Task { id: string; @@ -22,13 +23,15 @@ interface Task { async function getLeantimeUserId(email: string): Promise { try { if (!process.env.LEANTIME_TOKEN) { - console.error('LEANTIME_TOKEN is not set in environment variables'); + logger.error('[LEANTIME_TASKS] LEANTIME_TOKEN is not set in environment variables'); return null; } - console.log('Fetching Leantime users for email:', email); - console.log('API URL:', process.env.LEANTIME_API_URL); - console.log('Token length:', process.env.LEANTIME_TOKEN.length); + logger.debug('[LEANTIME_TASKS] Fetching Leantime users', { + emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12), + apiUrlPresent: !!process.env.LEANTIME_API_URL, + tokenLength: process.env.LEANTIME_TOKEN.length, + }); const headers: Record = { 'Content-Type': 'application/json', @@ -46,13 +49,12 @@ async function getLeantimeUserId(email: string): Promise { }); const responseText = await response.text(); - console.log('Raw Leantime response:', responseText); if (!response.ok) { - console.error('Failed to fetch Leantime users:', { + logger.error('[LEANTIME_TASKS] Failed to fetch Leantime users', { status: response.status, statusText: response.statusText, - headers: Object.fromEntries(response.headers.entries()) + headers: Object.fromEntries(response.headers.entries()), }); return null; } @@ -61,14 +63,14 @@ async function getLeantimeUserId(email: string): Promise { try { data = JSON.parse(responseText); } catch (e) { - console.error('Failed to parse Leantime response:', e); + logger.error('[LEANTIME_TASKS] Failed to parse Leantime response', { + error: e instanceof Error ? e.message : String(e), + }); return null; } - console.log('Leantime users response:', data); - if (!data.result || !Array.isArray(data.result)) { - console.error('Invalid response format from Leantime users API'); + logger.error('[LEANTIME_TASKS] Invalid response format from Leantime users API'); return null; } @@ -76,14 +78,21 @@ async function getLeantimeUserId(email: string): Promise { const user = users.find((u: any) => u.username === email); if (user) { - console.log('Found Leantime user:', { id: user.id, username: user.username }); + logger.debug('[LEANTIME_TASKS] Found Leantime user', { + id: user.id, + emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12), + }); } else { - console.log('No Leantime user found for username:', email); + logger.warn('[LEANTIME_TASKS] No Leantime user found for username', { + emailHash: Buffer.from(email.toLowerCase()).toString('base64').slice(0, 12), + }); } return user ? user.id : null; } catch (error) { - console.error('Error fetching Leantime user ID:', error); + logger.error('[LEANTIME_TASKS] Error fetching Leantime user ID', { + error: error instanceof Error ? error.message : String(error), + }); return null; } } @@ -103,20 +112,29 @@ export async function GET(request: NextRequest) { if (!forceRefresh) { const cachedTasks = await getCachedTasksData(session.user.id); if (cachedTasks) { - console.log(`Using cached tasks data for user ${session.user.id}`); + logger.debug('[LEANTIME_TASKS] Using cached tasks data', { + userId: session.user.id, + taskCount: Array.isArray(cachedTasks) ? cachedTasks.length : undefined, + }); return NextResponse.json(cachedTasks); } } - console.log('Fetching tasks for user:', session.user.email); + logger.debug('[LEANTIME_TASKS] Fetching tasks for user', { + emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12), + }); const userId = await getLeantimeUserId(session.user.email); if (!userId) { - console.error('User not found in Leantime'); + logger.error('[LEANTIME_TASKS] User not found in Leantime', { + emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12), + }); return NextResponse.json({ error: "User not found in Leantime" }, { status: 404 }); } - console.log('Fetching tasks for Leantime user ID:', userId); + logger.debug('[LEANTIME_TASKS] Fetching tasks for Leantime user ID', { + userId, + }); const headers: Record = { 'Content-Type': 'application/json', 'X-API-Key': process.env.LEANTIME_TOKEN! @@ -137,12 +155,14 @@ export async function GET(request: NextRequest) { }); const responseText = await response.text(); - console.log('Tasks API response status:', response.status); + logger.debug('[LEANTIME_TASKS] Tasks API response status', { + status: response.status, + }); if (!response.ok) { - console.error('Failed to fetch tasks from Leantime:', { + logger.error('[LEANTIME_TASKS] Failed to fetch tasks from Leantime', { status: response.status, - statusText: response.statusText + statusText: response.statusText, }); throw new Error('Failed to fetch tasks from Leantime'); } @@ -151,33 +171,27 @@ export async function GET(request: NextRequest) { try { data = JSON.parse(responseText); } catch (e) { - console.error('Failed to parse tasks response'); + logger.error('[LEANTIME_TASKS] Failed to parse tasks response', { + error: e instanceof Error ? e.message : String(e), + }); throw new Error('Invalid response format from Leantime'); } if (!data.result || !Array.isArray(data.result)) { - console.error('Invalid response format from Leantime tasks API'); + logger.error('[LEANTIME_TASKS] Invalid response format from Leantime tasks API'); throw new Error('Invalid response format from Leantime'); } // Log only the number of tasks and their IDs - console.log('Received tasks count:', data.result.length); - console.log('Task IDs:', data.result.map((task: any) => task.id)); + logger.debug('[LEANTIME_TASKS] Received tasks summary', { + count: data.result.length, + idsSample: data.result.slice(0, 20).map((task: any) => task.id), + }); const tasks = data.result .filter((task: any) => { - // Log raw task data for debugging - console.log('Raw task data:', { - id: task.id, - headline: task.headline, - status: task.status, - type: task.type, - dependingTicketId: task.dependingTicketId - }); - // Filter out any task (main or subtask) that has status Done (5) if (task.status === 5) { - console.log(`Filtering out Done task ${task.id} (type: ${task.type || 'main'}, status: ${task.status})`); return false; } @@ -187,7 +201,6 @@ export async function GET(request: NextRequest) { // Only show tasks where the user is the editor const isUserEditor = taskEditorId === currentUserId; - console.log(`Task ${task.id}: status=${task.status}, type=${task.type || 'main'}, parentId=${task.dependingTicketId || 'none'}, isUserEditor=${isUserEditor}`); return isUserEditor; }) .map((task: any) => ({ @@ -208,14 +221,19 @@ export async function GET(request: NextRequest) { dependingTicketId: task.dependingTicketId || null // Added parent task reference })); - console.log(`Found ${tasks.length} tasks assigned to user ${userId}`); + logger.debug('[LEANTIME_TASKS] Filtered tasks for user', { + userId, + count: tasks.length, + }); // Cache the results await cacheTasksData(session.user.id, tasks); return NextResponse.json(tasks); } catch (error) { - console.error('Error in tasks route:', error); + logger.error('[LEANTIME_TASKS] Error in tasks route', { + error: error instanceof Error ? error.message : String(error), + }); return NextResponse.json( { error: "Failed to fetch tasks" }, { status: 500 } diff --git a/app/api/news/route.ts b/app/api/news/route.ts index 690a90f1..9b6fca60 100644 --- a/app/api/news/route.ts +++ b/app/api/news/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from 'next/server'; import { env } from '@/lib/env'; import { getCachedNewsData, cacheNewsData } from '@/lib/redis'; +import { logger } from '@/lib/logger'; // Helper function to clean HTML content function cleanHtmlContent(text: string): string { @@ -91,21 +92,26 @@ export async function GET(request: Request) { // Also bypass cache if a non-default limit is explicitly requested const bypassCache = forceRefresh || (url.searchParams.has('limit') && limit !== '100'); - console.log(`News API request: limit=${limit}, forceRefresh=${forceRefresh}, bypassCache=${bypassCache}`); + logger.debug('[NEWS] Request received', { + limit, + forceRefresh, + bypassCache, + }); // Try to get data from cache if not forcing refresh if (!bypassCache) { const cachedNews = await getCachedNewsData(limit); if (cachedNews) { - console.log(`Using cached news data (${cachedNews.length} articles)`); + logger.debug('[NEWS] Using cached news data', { + limit, + count: cachedNews.length, + }); return NextResponse.json(cachedNews); } } - console.log(`Fetching news from FastAPI server with limit=${limit}...`); - const apiUrl = `${env.NEWS_API_URL}/news?limit=${limit}`; - console.log(`Full API URL: ${apiUrl}`); + logger.debug('[NEWS] Fetching from backend', { apiUrl }); const response = await fetch(apiUrl, { method: 'GET', @@ -117,10 +123,13 @@ export async function GET(request: Request) { }); if (!response.ok) { - console.error(`News API error: ${response.status} ${response.statusText}`); + logger.error('[NEWS] Backend API error', { + status: response.status, + statusText: response.statusText, + }); const contentType = response.headers.get('content-type'); if (contentType && !contentType.includes('application/json')) { - console.error('News API returned non-JSON response'); + logger.error('[NEWS] Backend returned non-JSON response'); return NextResponse.json( { error: 'News API returned invalid response format', status: response.status }, { status: 502 } @@ -135,15 +144,21 @@ export async function GET(request: Request) { let articles; try { articles = await response.json(); - console.log(`News API returned ${articles.length} articles with limit=${limit}`); - console.log(`First article: ${JSON.stringify(articles[0])}`); - console.log(`API URL used: ${apiUrl}`); + logger.debug('[NEWS] Backend response summary', { + limit, + count: articles.length, + }); if (articles.length < parseInt(limit) && articles.length > 0) { - console.log(`WARNING: API returned fewer articles (${articles.length}) than requested (${limit}). This suggests a backend limit.`); + logger.debug('[NEWS] Backend returned fewer articles than requested', { + requested: limit, + actual: articles.length, + }); } } catch (error) { - console.error('Failed to parse news API response:', error); + logger.error('[NEWS] Failed to parse backend response', { + error: error instanceof Error ? error.message : 'Unknown error', + }); return NextResponse.json( { error: 'Failed to parse news API response', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 502 } @@ -161,14 +176,19 @@ export async function GET(request: Request) { url: article.url })); - console.log(`Formatted and returning ${formattedNews.length} news articles`); + logger.debug('[NEWS] Returning formatted news', { + count: formattedNews.length, + limit, + }); // Cache the results await cacheNewsData(formattedNews, limit); return NextResponse.json(formattedNews); } catch (error) { - console.error('News API error:', error); + logger.error('[NEWS] Route error', { + error: error instanceof Error ? error.message : 'Unknown error', + }); return NextResponse.json( { error: 'Failed to fetch news', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } diff --git a/app/api/rocket-chat/messages/route.ts b/app/api/rocket-chat/messages/route.ts index e90477b3..35f63ccf 100644 --- a/app/api/rocket-chat/messages/route.ts +++ b/app/api/rocket-chat/messages/route.ts @@ -2,6 +2,7 @@ import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/options"; import { NextResponse } from "next/server"; import { getCachedMessagesData, cacheMessagesData } from "@/lib/redis"; +import { logger } from "@/lib/logger"; // Helper function to get user token using admin credentials async function getUserToken(baseUrl: string) { @@ -52,18 +53,20 @@ export async function GET(request: Request) { if (!forceRefresh) { const cachedMessages = await getCachedMessagesData(session.user.id); if (cachedMessages) { - console.log(`Using cached messages data for user ${session.user.id}`); + logger.debug("[ROCKET_CHAT] Using cached messages data", { + userId: session.user.id, + }); return NextResponse.json(cachedMessages); } } const baseUrl = process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL?.split('/channel')[0]; if (!baseUrl) { - console.error('Failed to get Rocket.Chat base URL'); + logger.error('[ROCKET_CHAT] Failed to get Rocket.Chat base URL'); return NextResponse.json({ error: 'Server configuration error' }, { status: 500 }); } - console.log('Using Rocket.Chat base URL:', baseUrl); + logger.debug('[ROCKET_CHAT] Using Rocket.Chat base URL', { baseUrl }); // Step 1: Use admin token to authenticate const adminHeaders = { @@ -75,7 +78,9 @@ export async function GET(request: Request) { // Step 2: Get the current user's Rocket.Chat ID const username = session.user.email.split('@')[0]; if (!username) { - console.error('No username found in session email'); + logger.error('[ROCKET_CHAT] No username found in session email', { + email: session.user.email, + }); return NextResponse.json({ messages: [] }, { status: 200 }); } @@ -86,15 +91,17 @@ export async function GET(request: Request) { }); if (!usersResponse.ok) { - console.error('Failed to get users list:', usersResponse.status); + logger.error('[ROCKET_CHAT] Failed to get users list', { + status: usersResponse.status, + }); return NextResponse.json({ messages: [] }, { status: 200 }); } const usersData = await usersResponse.json(); - console.log('Users list response:', { + logger.debug('[ROCKET_CHAT] Users list summary', { success: usersData.success, count: usersData.count, - usersCount: usersData.users?.length + usersCount: usersData.users?.length, }); // Find the current user in the list @@ -103,13 +110,16 @@ export async function GET(request: Request) { ); if (!currentUser) { - console.error('User not found in users list'); + logger.error('[ROCKET_CHAT] User not found in users list', { + username, + email: session.user.email, + }); return NextResponse.json({ messages: [] }, { status: 200 }); } - console.log('Found Rocket.Chat user:', { + logger.debug('[ROCKET_CHAT] Found user', { username: currentUser.username, - id: currentUser._id + id: currentUser._id, }); // Step 3: Create a token for the current user @@ -122,9 +132,13 @@ export async function GET(request: Request) { }); if (!createTokenResponse.ok) { - console.error('Failed to create user token:', createTokenResponse.status); + logger.error('[ROCKET_CHAT] Failed to create user token', { + status: createTokenResponse.status, + }); const errorText = await createTokenResponse.text(); - console.error('Create token error details:', errorText); + logger.error('[ROCKET_CHAT] Create token error details (truncated)', { + bodyPreview: errorText.substring(0, 200), + }); return NextResponse.json({ messages: [] }, { status: 200 }); } @@ -144,16 +158,20 @@ export async function GET(request: Request) { }); if (!subscriptionsResponse.ok) { - console.error('Failed to get subscriptions:', subscriptionsResponse.status); + logger.error('[ROCKET_CHAT] Failed to get subscriptions', { + status: subscriptionsResponse.status, + }); const errorText = await subscriptionsResponse.text(); - console.error('Subscriptions error details:', errorText); + logger.error('[ROCKET_CHAT] Subscriptions error details (truncated)', { + bodyPreview: errorText.substring(0, 200), + }); return NextResponse.json({ messages: [] }, { status: 200 }); } const subscriptionsData = await subscriptionsResponse.json(); if (!subscriptionsData.success || !Array.isArray(subscriptionsData.update)) { - console.error('Invalid subscriptions response structure'); + logger.error('[ROCKET_CHAT] Invalid subscriptions response structure'); return NextResponse.json({ messages: [] }, { status: 200 }); } @@ -167,18 +185,10 @@ export async function GET(request: Request) { return ['d', 'c', 'p'].includes(sub.t); }); - console.log('Filtered user subscriptions:', { + logger.debug('[ROCKET_CHAT] Filtered user subscriptions', { userId: currentUser._id, username: currentUser.username, totalSubscriptions: userSubscriptions.length, - subscriptionDetails: userSubscriptions.map((sub: any) => ({ - type: sub.t, - name: sub.fname || sub.name, - rid: sub.rid, - alert: sub.alert, - unread: sub.unread, - userMentions: sub.userMentions - })) }); const messages: any[] = []; @@ -217,15 +227,19 @@ export async function GET(request: Request) { ); if (!messagesResponse.ok) { - console.error(`Failed to get messages for room ${subscription.name}:`, messagesResponse.status); + logger.error('[ROCKET_CHAT] Failed to get messages for room', { + roomName: subscription.name, + status: messagesResponse.status, + }); continue; } const messageData = await messagesResponse.json(); - console.log(`Messages for room ${subscription.fname || subscription.name}:`, { + logger.debug('[ROCKET_CHAT] Messages for room', { + roomName: subscription.fname || subscription.name, success: messageData.success, count: messageData.count, - hasMessages: messageData.messages?.length > 0 + hasMessages: messageData.messages?.length > 0, }); if (messageData.success && messageData.messages?.length > 0) { @@ -337,7 +351,10 @@ export async function GET(request: Request) { } } } catch (error) { - console.error(`Error fetching messages for room ${subscription.name}:`, error); + logger.error('[ROCKET_CHAT] Error fetching messages for room', { + roomName: subscription.name, + error: error instanceof Error ? error.message : String(error), + }); continue; } } @@ -358,7 +375,9 @@ export async function GET(request: Request) { return NextResponse.json(finalResponse); } catch (error) { - console.error('Error fetching messages:', error); + logger.error('[ROCKET_CHAT] Error fetching messages', { + error: error instanceof Error ? error.message : String(error), + }); return NextResponse.json({ error: 'Failed to fetch messages' }, { status: 500 }); } } diff --git a/lib/services/microsoft-oauth.ts b/lib/services/microsoft-oauth.ts index 58e8b47c..18a5f9b3 100644 --- a/lib/services/microsoft-oauth.ts +++ b/lib/services/microsoft-oauth.ts @@ -12,14 +12,7 @@ const clientId = process.env.MICROSOFT_CLIENT_ID; const clientSecret = process.env.MICROSOFT_CLIENT_SECRET; const redirectUri = process.env.MICROSOFT_REDIRECT_URI; -// Log configuration for debugging -console.log('Microsoft OAuth Configuration:', { - tenantId, - authorizeUrl: MICROSOFT_AUTHORIZE_URL, - tokenUrl: MICROSOFT_TOKEN_URL, - clientIdFirstChars: clientId ? clientId.substring(0, 5) + '...' : 'undefined', - redirectUri -}); +// NOTE: In production we do not log Microsoft OAuth configuration to avoid noise and potential leakage. // Required scopes for IMAP and SMTP access const REQUIRED_SCOPES = [