Refactor Logging BIG

This commit is contained in:
alma 2026-01-07 10:49:57 +01:00
parent c1ab0d5f81
commit 9ee441773c
4 changed files with 139 additions and 89 deletions

View File

@ -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<number | null> {
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<string, string> = {
'Content-Type': 'application/json',
@ -46,13 +49,12 @@ async function getLeantimeUserId(email: string): Promise<number | null> {
});
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<number | null> {
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<number | null> {
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<string, string> = {
'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 }

View File

@ -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 }

View File

@ -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 });
}
}

View File

@ -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 = [