From 160cb3927d3321f17656342e4c5e8e42f43dffff Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Thu, 24 Jul 2025 18:49:31 +0200 Subject: [PATCH] testapp: enhance logging and error handling across services and controllers --- .github/workflows/wc-e2e-test.yaml | 63 ++++++++++++++++++++ testapp/src/controllers/homeController.ts | 70 +++++++++++++++++++---- testapp/src/index.ts | 64 +++++++++++++++++++++ testapp/src/services/recordingService.ts | 56 ++++++++++++------ testapp/src/services/roomService.ts | 61 +++++++++++++++----- testapp/src/utils/http.ts | 39 +++++++++---- 6 files changed, 301 insertions(+), 52 deletions(-) diff --git a/.github/workflows/wc-e2e-test.yaml b/.github/workflows/wc-e2e-test.yaml index 96a3009..ff56eef 100644 --- a/.github/workflows/wc-e2e-test.yaml +++ b/.github/workflows/wc-e2e-test.yaml @@ -140,6 +140,20 @@ jobs: env: RUN_MODE: CI PLAYWRIGHT_BROWSERS_PATH: /tmp/ms-playwright + - name: Collect TestApp logs + if: always() + run: | + echo "=== Collecting TestApp logs ===" + if [ -f testapp/testapp.log ]; then + echo "TestApp log file found, copying..." + cp testapp/testapp.log testapp-logs-webhooks.log + else + echo "TestApp log file not found at testapp/testapp.log" + echo "Looking for log files..." + find . -name "testapp.log" -type f 2>/dev/null || echo "No testapp.log files found" + echo "Creating empty log file for artifact upload" + echo "TestApp log file not found during CI run" > testapp-logs-webhooks.log + fi - name: Upload failed test videos if: always() uses: actions/upload-artifact@v4 @@ -148,6 +162,13 @@ jobs: path: | frontend/webcomponent/test-results/ retention-days: 2 + - name: Upload TestApp logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: testapp-logs-webhooks-functionality + path: testapp-logs-webhooks.log + retention-days: 2 - name: Clean up if: always() uses: OpenVidu/actions/cleanup@main @@ -190,6 +211,20 @@ jobs: env: RUN_MODE: CI PLAYWRIGHT_BROWSERS_PATH: /tmp/ms-playwright + - name: Collect TestApp logs + if: always() + run: | + echo "=== Collecting TestApp logs ===" + if [ -f testapp/testapp.log ]; then + echo "TestApp log file found, copying..." + cp testapp/testapp.log testapp-logs-ui-features.log + else + echo "TestApp log file not found at testapp/testapp.log" + echo "Looking for log files..." + find . -name "testapp.log" -type f 2>/dev/null || echo "No testapp.log files found" + echo "Creating empty log file for artifact upload" + echo "TestApp log file not found during CI run" > testapp-logs-ui-features.log + fi - name: Upload failed test videos if: always() uses: actions/upload-artifact@v4 @@ -198,6 +233,13 @@ jobs: path: | frontend/webcomponent/test-results/ retention-days: 2 + - name: Upload TestApp logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: testapp-logs-ui-features + path: testapp-logs-ui-features.log + retention-days: 2 - name: Clean up if: always() uses: OpenVidu/actions/cleanup@main @@ -240,6 +282,20 @@ jobs: env: RUN_MODE: CI PLAYWRIGHT_BROWSERS_PATH: /tmp/ms-playwright + - name: Collect TestApp logs + if: always() + run: | + echo "=== Collecting TestApp logs ===" + if [ -f testapp/testapp.log ]; then + echo "TestApp log file found, copying..." + cp testapp/testapp.log testapp-logs-recording-access.log + else + echo "TestApp log file not found at testapp/testapp.log" + echo "Looking for log files..." + find . -name "testapp.log" -type f 2>/dev/null || echo "No testapp.log files found" + echo "Creating empty log file for artifact upload" + echo "TestApp log file not found during CI run" > testapp-logs-recording-access.log + fi - name: Dump TestApp logs on failure if: failure() run: | @@ -256,6 +312,13 @@ jobs: path: | frontend/webcomponent/test-results/ retention-days: 2 + - name: Upload TestApp logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: testapp-logs-recording-access + path: testapp-logs-recording-access.log + retention-days: 2 - name: Dump OpenVidu Local Deployment logs if: always() shell: bash diff --git a/testapp/src/controllers/homeController.ts b/testapp/src/controllers/homeController.ts index 3b35c4b..e713920 100644 --- a/testapp/src/controllers/homeController.ts +++ b/testapp/src/controllers/homeController.ts @@ -4,6 +4,9 @@ import { deleteAllRecordings, getAllRecordings } from '../services/recordingServ export const getHome = async (_req: Request, res: Response) => { try { + console.log('Fetching rooms from:', `${process.env.MEET_API_URL || 'http://localhost:6080/api/v1'}/rooms`); + console.log('Using API key:', process.env.MEET_API_KEY || 'meet-api-key'); + const { rooms } = await getAllRooms(); // Sort rooms by newest first @@ -13,44 +16,78 @@ export const getHome = async (_req: Request, res: Response) => { return dateB.getTime() - dateA.getTime(); }); - console.log(`Rooms fetched: ${rooms.length}`); + console.log(`Rooms fetched successfully: ${rooms.length}`); res.render('index', { rooms }); } catch (error) { console.error('Error fetching rooms:', error); - res.status(500).send('Internal Server Error'); + console.error('Error details:', { + message: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : 'No stack trace', + apiUrl: process.env.MEET_API_URL || 'http://localhost:6080/api/v1', + apiKey: process.env.MEET_API_KEY || 'meet-api-key' + }); + res.status(500).send('Internal Server Error - Failed to fetch rooms: ' + (error instanceof Error ? error.message : 'Unknown error')); return; } }; export const postCreateRoom = async (req: Request, res: Response) => { try { + console.log('Creating room with body:', JSON.stringify(req.body, null, 2)); const { roomIdPrefix, autoDeletionDate } = req.body; const preferences = processFormPreferences(req.body); - await createRoom({ roomIdPrefix, autoDeletionDate, preferences }); + console.log('Processed preferences:', JSON.stringify(preferences, null, 2)); + console.log('Room creation parameters:', { roomIdPrefix, autoDeletionDate }); + + const result = await createRoom({ roomIdPrefix, autoDeletionDate, preferences }); + console.log('Room created successfully:', result); res.redirect('/'); } catch (error) { console.error('Error creating room:', error); - res.status(500).send('Internal Server Error'); + console.error('Error details:', { + message: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : 'No stack trace', + requestBody: req.body + }); + res.status(500).send('Internal Server Error - Failed to create room: ' + (error instanceof Error ? error.message : 'Unknown error')); return; } }; export const deleteRoomCtrl = async (req: Request, res: Response) => { try { + console.log('Deleting room with body:', JSON.stringify(req.body, null, 2)); const { roomId } = req.body; + + if (!roomId) { + console.error('No roomId provided for deletion'); + res.status(400).send('Bad Request - roomId is required'); + return; + } + + console.log(`Attempting to delete room: ${roomId}`); await deleteRoom(roomId); + console.log(`Room ${roomId} deleted successfully`); res.redirect('/'); } catch (error) { console.error('Error deleting room:', error); - res.status(500).send('Internal Server Error'); + console.error('Error details:', { + message: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : 'No stack trace', + requestBody: req.body + }); + res.status(500).send('Internal Server Error - Failed to delete room: ' + (error instanceof Error ? error.message : 'Unknown error')); return; } }; export const deleteAllRoomsCtrl = async (_req: Request, res: Response) => { try { + console.log('Deleting all rooms...'); const allRooms = await getAllRooms(); + console.log(`Found ${allRooms.rooms.length} rooms to delete`); + if (allRooms.rooms.length === 0) { console.log('No rooms to delete'); res.render('index', { rooms: [] }); @@ -58,32 +95,45 @@ export const deleteAllRoomsCtrl = async (_req: Request, res: Response) => { } const roomIds = allRooms.rooms.map((room) => room.roomId); - console.log(`Deleting ${roomIds.length} rooms`, roomIds); + console.log(`Deleting ${roomIds.length} rooms:`, roomIds); await deleteAllRooms(roomIds); + console.log('All rooms deleted successfully'); res.render('index', { rooms: [] }); } catch (error) { console.error('Error deleting all rooms:', error); - res.status(500).send('Internal Server Error ' + JSON.stringify(error)); + console.error('Error details:', { + message: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : 'No stack trace' + }); + res.status(500).send('Internal Server Error - Failed to delete all rooms: ' + (error instanceof Error ? error.message : 'Unknown error')); return; } }; export const deleteAllRecordingsCtrl = async (_req: Request, res: Response) => { try { + console.log('Deleting all recordings...'); const [{ recordings }, { rooms }] = await Promise.all([getAllRecordings(), getAllRooms()]); + console.log(`Found ${recordings.length} recordings to delete`); + if (recordings.length === 0) { console.log('No recordings to delete'); res.render('index', { rooms }); return; } - + const recordingIds = recordings.map((recording) => recording.recordingId); + console.log(`Deleting ${recordingIds.length} recordings:`, recordingIds); await deleteAllRecordings(recordingIds); - console.log(`Deleted ${recordingIds.length} recordings`); + console.log('All recordings deleted successfully'); res.render('index', { rooms }); } catch (error) { console.error('Error deleting all recordings:', error); - res.status(500).send('Internal Server Error ' + JSON.stringify(error)); + console.error('Error details:', { + message: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : 'No stack trace' + }); + res.status(500).send('Internal Server Error - Failed to delete all recordings: ' + (error instanceof Error ? error.message : 'Unknown error')); return; } }; diff --git a/testapp/src/index.ts b/testapp/src/index.ts index faa133c..a118f86 100644 --- a/testapp/src/index.ts +++ b/testapp/src/index.ts @@ -1,6 +1,7 @@ import express from 'express'; import http from 'http'; import path from 'path'; +import fs from 'fs'; import { Server as IOServer } from 'socket.io'; import { deleteAllRecordingsCtrl, @@ -12,6 +13,43 @@ import { import { handleWebhook, joinRoom } from './controllers/roomController'; import { configService } from './services/configService'; +// Setup log file for CI debugging +const logStream = fs.createWriteStream(path.join(__dirname, '../../testapp.log'), { flags: 'a' }); + +// Override console methods to also write to log file +const originalConsoleLog = console.log; +const originalConsoleError = console.error; +const originalConsoleWarn = console.warn; + +console.log = (...args: any[]) => { + const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg).join(' '); + const timestamp = new Date().toISOString(); + logStream.write(`[${timestamp}] LOG: ${message}\n`); + originalConsoleLog.apply(console, args); +}; + +console.error = (...args: any[]) => { + const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg).join(' '); + const timestamp = new Date().toISOString(); + logStream.write(`[${timestamp}] ERROR: ${message}\n`); + originalConsoleError.apply(console, args); +}; + +console.warn = (...args: any[]) => { + const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg).join(' '); + const timestamp = new Date().toISOString(); + logStream.write(`[${timestamp}] WARN: ${message}\n`); + originalConsoleWarn.apply(console, args); +}; + +console.log('=== TESTAPP STARTUP ==='); +console.log('Testapp initializing at:', new Date().toISOString()); +console.log('Environment variables:'); +console.log('MEET_API_URL:', process.env.MEET_API_URL); +console.log('MEET_API_KEY:', process.env.MEET_API_KEY ? '***SET***' : 'NOT SET'); +console.log('NODE_ENV:', process.env.NODE_ENV); +console.log('PORT:', process.env.PORT); + const app = express(); const server = http.createServer(app); const io = new IOServer(server); @@ -46,10 +84,36 @@ server.listen(PORT, () => { console.log(`Server running on port ${PORT}`); console.log(`Visit http://localhost:${PORT}/ to access the app`); console.log('-----------------------------------------'); + console.log('Configuration:'); + console.log(` MEET_API_URL: ${configService.meetApiUrl}`); + console.log(` MEET_API_KEY: ${configService.meetApiKey ? '[SET]' : '[NOT SET]'}`); + console.log(` MEET_WEBCOMPONENT_SRC: ${configService.meetWebhookSrc}`); + console.log(` SERVER_PORT: ${configService.serverPort}`); + console.log('-----------------------------------------'); console.log(''); console.log('OpenVidu Meet Configuration:'); console.log(`Meet API URL: ${configService.meetApiUrl}`); console.log(`Meet API key: ${configService.meetApiKey}`); console.log(`Meet Webcomponent Source: ${configService.meetWebhookSrc}`); + console.log('=== TESTAPP STARTUP COMPLETE ==='); +}); + +// Handle graceful shutdown +process.on('SIGTERM', () => { + console.log('=== TESTAPP SHUTDOWN ==='); + console.log('Received SIGTERM, shutting down gracefully'); + logStream.end(); + server.close(() => { + process.exit(0); + }); +}); + +process.on('SIGINT', () => { + console.log('=== TESTAPP SHUTDOWN ==='); + console.log('Received SIGINT, shutting down gracefully'); + logStream.end(); + server.close(() => { + process.exit(0); + }); }); diff --git a/testapp/src/services/recordingService.ts b/testapp/src/services/recordingService.ts index 7078693..eaed9f5 100644 --- a/testapp/src/services/recordingService.ts +++ b/testapp/src/services/recordingService.ts @@ -8,32 +8,52 @@ export const getAllRecordings = async (): Promise<{ recordings: MeetRecordingInfo[]; }> => { const url = `${configService.meetApiUrl}/recordings`; + console.log(`Fetching all recordings from: ${url}`); - let { pagination, recordings } = await get<{ - pagination: any; - recordings: MeetRecordingInfo[]; - }>(url, { - headers: { 'x-api-key': configService.meetApiKey } - }); - - while (pagination.isTruncated) { - const nextPageUrl = `${url}?nextPageToken=${pagination.nextPageToken}`; - const nextPageResult = await get<{ + try { + let { pagination, recordings } = await get<{ pagination: any; recordings: MeetRecordingInfo[]; - }>(nextPageUrl, { + }>(url, { headers: { 'x-api-key': configService.meetApiKey } }); - recordings.push(...nextPageResult.recordings); - pagination = nextPageResult.pagination; - } - return { pagination, recordings }; + console.log(`Fetched initial page with ${recordings.length} recordings, isTruncated: ${pagination.isTruncated}`); + + while (pagination.isTruncated) { + const nextPageUrl = `${url}?nextPageToken=${pagination.nextPageToken}`; + console.log(`Fetching next page from: ${nextPageUrl}`); + const nextPageResult = await get<{ + pagination: any; + recordings: MeetRecordingInfo[]; + }>(nextPageUrl, { + headers: { 'x-api-key': configService.meetApiKey } + }); + recordings.push(...nextPageResult.recordings); + pagination = nextPageResult.pagination; + console.log(`Fetched additional ${nextPageResult.recordings.length} recordings, total: ${recordings.length}`); + } + + console.log(`Successfully fetched total of ${recordings.length} recordings`); + return { pagination, recordings }; + } catch (error) { + console.error('Error fetching all recordings:', error); + throw error; + } }; export const deleteAllRecordings = async (recordingIds: string[]): Promise => { const url = `${configService.meetApiUrl}/recordings?recordingIds=${recordingIds.join(',')}`; - await del(url, { - headers: { 'x-api-key': configService.meetApiKey } - }); + console.log(`Deleting ${recordingIds.length} recordings from: ${url}`); + console.log('Recording IDs to delete:', recordingIds); + try { + await del(url, { + headers: { 'x-api-key': configService.meetApiKey } + }); + console.log(`Successfully deleted ${recordingIds.length} recordings`); + } catch (error) { + console.error(`Error deleting ${recordingIds.length} recordings:`, error); + console.error('Recording IDs that failed:', recordingIds); + throw error; + } }; diff --git a/testapp/src/services/roomService.ts b/testapp/src/services/roomService.ts index 769ba35..f0e43a6 100644 --- a/testapp/src/services/roomService.ts +++ b/testapp/src/services/roomService.ts @@ -8,9 +8,17 @@ export async function getAllRooms(): Promise<{ rooms: MeetRoom[]; }> { const url = `${configService.meetApiUrl}/rooms`; - return get<{ pagination: any; rooms: MeetRoom[] }>(url, { - headers: { 'x-api-key': configService.meetApiKey } - }); + console.log(`Fetching all rooms from: ${url}`); + try { + const result = await get<{ pagination: any; rooms: MeetRoom[] }>(url, { + headers: { 'x-api-key': configService.meetApiKey } + }); + console.log(`Successfully fetched ${result.rooms.length} rooms`); + return result; + } catch (error) { + console.error('Error fetching all rooms:', error); + throw error; + } } export async function createRoom(roomData: MeetRoomOptions): Promise { @@ -23,23 +31,48 @@ export async function createRoom(roomData: MeetRoomOptions): Promise { roomData.autoDeletionDate = new Date(roomData.autoDeletionDate).getTime(); } - console.log('Creating room with options:', roomData); - return post(url, { - headers: { 'x-api-key': configService.meetApiKey }, - body: roomData - }); + console.log('Creating room with options:', JSON.stringify(roomData, null, 2)); + console.log(`Making POST request to: ${url}`); + try { + const result = await post(url, { + headers: { 'x-api-key': configService.meetApiKey }, + body: roomData + }); + console.log('Room created successfully:', JSON.stringify(result, null, 2)); + return result; + } catch (error) { + console.error('Error creating room:', error); + console.error('Room data that failed:', JSON.stringify(roomData, null, 2)); + throw error; + } } export async function deleteRoom(roomId: string): Promise { const url = `${configService.meetApiUrl}/rooms/${roomId}`; - await del(url, { - headers: { 'x-api-key': configService.meetApiKey } - }); + console.log(`Deleting room ${roomId} from: ${url}`); + try { + await del(url, { + headers: { 'x-api-key': configService.meetApiKey } + }); + console.log(`Room ${roomId} deleted successfully`); + } catch (error) { + console.error(`Error deleting room ${roomId}:`, error); + throw error; + } } export async function deleteAllRooms(roomIds: string[]): Promise { const url = `${configService.meetApiUrl}/rooms?roomIds=${roomIds.join(',')}`; - await del(url, { - headers: { 'x-api-key': configService.meetApiKey } - }); + console.log(`Deleting ${roomIds.length} rooms from: ${url}`); + console.log('Room IDs to delete:', roomIds); + try { + await del(url, { + headers: { 'x-api-key': configService.meetApiKey } + }); + console.log(`Successfully deleted ${roomIds.length} rooms`); + } catch (error) { + console.error(`Error deleting ${roomIds.length} rooms:`, error); + console.error('Room IDs that failed:', roomIds); + throw error; + } } diff --git a/testapp/src/utils/http.ts b/testapp/src/utils/http.ts index ff5fbd7..b9da82d 100644 --- a/testapp/src/utils/http.ts +++ b/testapp/src/utils/http.ts @@ -18,19 +18,38 @@ async function request(method: string, url: string, options: RequestOptions = body: options.body ? JSON.stringify(options.body) : undefined }; - const response = await fetch(fullUrl, fetchOptions); - if (!response.ok) { + console.log(`Making ${method} request to: ${fullUrl}`); + console.log('Request headers:', fetchOptions.headers); + + try { + const response = await fetch(fullUrl, fetchOptions); + + console.log(`Response status: ${response.status} ${response.statusText}`); + + if (!response.ok) { + const text = await response.text(); + console.error(`HTTP Error ${response.status}:`, text); + throw new Error(`HTTP ${response.status} (${response.statusText}): ${text || 'No response body'}`); + } + + // Handle empty responses (e.g., for DELETE requests) const text = await response.text(); - throw new Error(`HTTP ${response.status}: ${text}`); - } + if (!text) { + console.log('Empty response received'); + return {} as T; + } - // Handle empty responses (e.g., for DELETE requests) - const text = await response.text(); - if (!text) { - return {} as T; - } + console.log('Response received successfully'); + return JSON.parse(text) as T; + } catch (error) { + console.error(`Request failed for ${method} ${fullUrl}:`, error); - return JSON.parse(text) as T; + if (error instanceof TypeError && error.message.includes('fetch')) { + throw new Error(`Network error: Unable to connect to ${fullUrl}. Check if the service is running.`); + } + + throw error; + } } export function get(url: string, options?: Omit): Promise {