Node.js 를 배워보자

Nuxt.js로 오라클 debug_log 테이블 실시간 모니터링 구현하기

_Blue_Sky_ 2025. 3. 17. 14:46
728x90

 

오라클 데이터베이스의 debug_log 테이블에 데이터가 삽입될 때마다 실시간으로 화면에 <table> 태그로 표시되도록 Nuxt.js 애플리케이션을 구현해보겠습니다. 이를 위해 서버에서 주기적으로 데이터를 폴링(polling)하거나, 가능하다면 웹소켓(WebSocket)을 사용해 실시간 업데이트를 처리할 수 있습니다. 여기서는 간단한 폴링 방식과 Nuxt 3를 활용한 예제를 블로그 형식으로 정리하겠습니다.

  

2025.03.17 - [Oracle Database 강좌] - 오라클에서 디버깅 로그를 테이블에 저장하기: 프로시저명과 라인 번호 추가

 
 목표
  • debug_log 테이블에 데이터가 추가될 때마다 Nuxt.js 페이지에서 실시간으로 테이블에 반영.
  • 주기적인 API 호출을 통해 데이터를 가져오고, <table>로 렌더링.
  • 간단한 UI와 데이터 관리 구현.

1. 사전 준비
먼저, 오라클의 debug_log 테이블과 Nuxt.js 프로젝트가 준비되어 있어야 합니다.
debug_log 테이블 (오라클)
CREATE TABLE debug_log (
    log_id NUMBER GENERATED ALWAYS AS IDENTITY,
    log_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    procedure_name VARCHAR2(100),
    line_number NUMBER,
    message VARCHAR2(4000)
);
Nuxt 프로젝트 설정
Nuxt 3 프로젝트가 없다면 아래 명령으로 생성합니다:
npx nuxi init nuxt-oracle-debug
cd nuxt-oracle-debug
npm install

 
2. 서버 API 구현
Nuxt 3의 서버 기능을 사용해 오라클 데이터베이스에서 debug_log 데이터를 가져오는 API를 만듭니다. 여기서는 oracledb 모듈을 사용합니다.
의존성 설치
npm install oracledb
API 엔드포인트 작성
server/api/debugLogs.js 파일을 생성하고 아래 코드를 추가합니다:
import oracledb from 'oracledb';

export default defineEventHandler(async () => {
  let connection;
  try {
    // 오라클 DB 연결
    connection = await oracledb.getConnection({
      user: 'your_username',
      password: 'your_password',
      connectString: 'your_connect_string' // 예: "localhost:1521/orcl"
    });

    // debug_log 테이블에서 데이터 조회
    const result = await connection.execute(
      `SELECT log_id, log_time, procedure_name, line_number, message 
       FROM debug_log 
       ORDER BY log_time DESC`
    );

    // 결과 반환
    return result.rows.map(row => ({
      logId: row[0],
      logTime: row[1].toISOString(), // TIMESTAMP를 문자열로 변환
      procedureName: row[2],
      lineNumber: row[3],
      message: row[4]
    }));
  } catch (err) {
    console.error('DB Error:', err);
    throw createError({ statusCode: 500, message: 'Database error' });
  } finally {
    if (connection) {
      await connection.close();
    }
  }
});
  • 설명: /api/debugLogs 엔드포인트가 최신 debug_log 데이터를 반환합니다.
  • 주의: your_username, your_password, your_connect_string을 실제 값으로 교체하세요.

 
3. Nuxt 페이지 구현
pages/index.vue 파일을 열고, 주기적으로 데이터를 가져와 <table>로 표시하는 코드를 작성합니다.
<!-- pages/index.vue -->
<template>
  <div>
    <h1>Debug Log Monitor</h1>
    <table border="1">
      <thead>
        <tr>
          <th>Log ID</th>
          <th>Time</th>
          <th>Procedure</th>
          <th>Line</th>
          <th>Message</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="log in logs" :key="log.logId">
          <td>{{ log.logId }}</td>
          <td>{{ log.logTime }}</td>
          <td>{{ log.procedureName }}</td>
          <td>{{ log.lineNumber }}</td>
          <td>{{ log.message }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

// 로그 데이터를 저장할 반응형 변수
const logs = ref([]);

// 데이터를 주기적으로 가져오는 함수
const fetchLogs = async () => {
  try {
    const response = await $fetch('/api/debugLogs');
    logs.value = response;
  } catch (err) {
    console.error('Fetch error:', err);
  }
};

// 폴링 설정 (2초마다 데이터 가져오기)
let intervalId = null;
onMounted(() => {
  fetchLogs(); // 초기 데이터 로드
  intervalId = setInterval(fetchLogs, 2000); // 2초마다 갱신
});

onUnmounted(() => {
  if (intervalId) clearInterval(intervalId); // 컴포넌트 언마운트 시 폴링 중지
});
</script>

<style scoped>
table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 20px;
}
th, td {
  padding: 8px;
  text-align: left;
}
th {
  background-color: #f2f2f2;
}
</style>

4. 실행 및 테스트
  1. Nuxt 애플리케이션 실행:
    npm run dev
  2. 브라우저에서 http://localhost:3000 접속.
  3. 오라클에서 debug_log에 데이터를 삽입하며 테스트:
    INSERT INTO debug_log (procedure_name, line_number, message)
    VALUES ('TEST_PROC', 10, 'Test message');
    COMMIT;
  • 결과: 2초마다 테이블이 갱신되며 새로운 로그가 화면에 나타납니다.

5. 개선 아이디어
  • 웹소켓 사용: 폴링 대신 서버에서 데이터 변경을 감지하고 웹소켓으로 클라이언트에 푸시하면 더 실시간에 가까운 반응 가능.
  • 최적화: log_id를 기준으로 새로운 데이터만 추가하도록 클라이언트 로직 개선.
  • 필터링: 로그 검색 기능 추가.
웹소켓 예제 (간단한 개요)
  1. 서버에서 socket.io 설치:
    npm install socket.io
  2. server/api에 웹소켓 로직 추가 후, 데이터베이스 트리거로 변경 감지 시 클라이언트에 전송.

6. 주의사항
  • 성능: 폴링 주기(2초)는 데이터 양과 서버 부하에 따라 조정 필요.
  • 보안: 실제 배포 시 API에 인증 추가.
  • 오라클 연결: oracledb 모듈이 로컬 환경에 맞게 설정되어야 함 (Oracle Instant Client 필요).

 

Nuxt.js와 WebSocket으로 오라클 debug_log 테이블 실시간 모니터링 구현하기

이전 글에서 폴링(polling) 방식으로 debug_log 테이블의 데이터를 Nuxt.js에서 실시간으로 표시하는 방법을 다뤘습니다. 이번에는 **웹소켓(WebSocket)**을 활용해 서버에서 데이터베이스 변경을 감지하고 클라이언트로 실시간 푸시하는 방법을 자세히 설명하겠습니다. WebSocket은 양방향 통신을 제공하므로 폴링보다 더 효율적이고 즉각적인 업데이트가 가능합니다.

 


목표
  • 오라클 debug_log 테이블에 데이터가 삽입될 때마다 WebSocket으로 클라이언트에 실시간 전송.
  • Nuxt.js에서 <table>로 데이터를 즉시 렌더링.
  • 서버와 클라이언트 간 실시간 연결 유지.

1. 사전 준비
debug_log 테이블
이전과 동일한 구조를 사용합니다:
CREATE TABLE debug_log (
    log_id NUMBER GENERATED ALWAYS AS IDENTITY,
    log_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    procedure_name VARCHAR2(100),
    line_number NUMBER,
    message VARCHAR2(4000)
);
Nuxt 프로젝트
Nuxt 3 프로젝트가 필요합니다. 없다면:
npx nuxi init nuxt-oracle-websocket
cd nuxt-oracle-websocket
npm install

2. 서버 측 WebSocket 구현
Nuxt 3의 서버에서 WebSocket을 처리하기 위해 socket.io를 사용합니다. 서버에서 데이터베이스 변경을 감지하고 클라이언트에 전송하는 로직을 추가합니다.
의존성 설치
npm install socket.io oracledb
서버 미들웨어 작성
server/middleware/websocket.js 파일을 생성해 WebSocket 서버를 설정합니다:
 
import { Server } from 'socket.io';
import oracledb from 'oracledb';

let io;

export default defineEventHandler(async (event) => {
  const server = event.node.res.socket.server;

  // WebSocket 서버 초기화 (최초 요청 시에만)
  if (!io) {
    io = new Server(server, {
      cors: { origin: '*' } // 개발용, 실제 배포 시 제한 필요
    });

    console.log('WebSocket server initialized');

    // 데이터베이스 연결 설정
    const connection = await oracledb.getConnection({
      user: 'your_username',
      password: 'your_password',
      connectString: 'your_connect_string' // 예: "localhost:1521/orcl"
    });

    // 클라이언트 연결 처리
    io.on('connection', (socket) => {
      console.log('Client connected:', socket.id);

      // 초기 데이터 전송
      sendInitialData(socket);

      socket.on('disconnect', () => {
        console.log('Client disconnected:', socket.id);
      });
    });

    // 데이터베이스 변경 감지 및 브로드캐스트 (수동 시뮬레이션)
    setInterval(async () => {
      const logs = await fetchLatestLogs(connection);
      io.emit('newLog', logs[logs.length - 1]); // 최신 로그만 전송
    }, 2000); // 실제 환경에서는 트리거로 대체
  }

  // 클라이언트 요청에 대한 응답 (필요 시)
  return { status: 'WebSocket ready' };
});

// 초기 데이터 전송 함수
async function sendInitialData(socket) {
  const connection = await oracledb.getConnection({
    user: 'your_username',
    password: 'your_password',
    connectString: 'your_connect_string'
  });
  const logs = await fetchLatestLogs(connection);
  socket.emit('initialLogs', logs);
  await connection.close();
}

// 최신 로그 가져오기 함수
async function fetchLatestLogs(connection) {
  const result = await connection.execute(
    `SELECT log_id, log_time, procedure_name, line_number, message 
     FROM debug_log 
     ORDER BY log_time DESC 
     FETCH FIRST 10 ROWS ONLY`
  );
  return result.rows.map(row => ({
    logId: row[0],
    logTime: row[1].toISOString(),
    procedureName: row[2],
    lineNumber: row[3],
    message: row[4]
  }));
}
  • 설명:
    • socket.io로 WebSocket 서버를 초기화.
    • 클라이언트 연결 시 초기 데이터를 전송하고, 주기적으로 최신 로그를 브로드캐스트.
    • 실제 환경에서는 주기적 폴링 대신 오라클 트리거와 DBMS_ALERT 또는 DBMS_PIPE를 사용해 변경을 감지할 수 있음 (이에 대해서는 아래에서 추가 설명).

3. 클라이언트 측 Nuxt 구현
pages/index.vue에서 WebSocket 클라이언트를 연결하고 실시간 데이터를 테이블에 표시합니다.
클라이언트 의존성 설치
npm install socket.io-client
페이지 코드
<!-- pages/index.vue -->
<template>
  <div>
    <h1>Debug Log Monitor (WebSocket)</h1>
    <table border="1">
      <thead>
        <tr>
          <th>Log ID</th>
          <th>Time</th>
          <th>Procedure</th>
          <th>Line</th>
          <th>Message</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="log in logs" :key="log.logId">
          <td>{{ log.logId }}</td>
          <td>{{ log.logTime }}</td>
          <td>{{ log.procedureName }}</td>
          <td>{{ log.lineNumber }}</td>
          <td>{{ log.message }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { io } from 'socket.io-client';

// 로그 데이터를 저장할 반응형 변수
const logs = ref([]);

// WebSocket 연결 설정
let socket;

onMounted(() => {
  // WebSocket 클라이언트 초기화
  socket = io('http://localhost:3000');

  // 초기 데이터 수신
  socket.on('initialLogs', (initialLogs) => {
    logs.value = initialLogs;
  });

  // 새 로그 수신 시 추가
  socket.on('newLog', (newLog) => {
    logs.value.unshift(newLog); // 최신 로그를 맨 위에 추가
    if (logs.value.length > 50) logs.value.pop(); // 최대 50개 유지
  });
});

onUnmounted(() => {
  if (socket) socket.disconnect(); // 컴포넌트 언마운트 시 연결 해제
});
</script>

<style scoped>
table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 20px;
}
th, td {
  padding: 8px;
  text-align: left;
}
th {
  background-color: #f2f2f2;
}
</style>
  •  
    • socket.io-client로 WebSocket 연결.
    • initialLogs 이벤트로 초기 데이터를 받고, newLog 이벤트로 새 로그를 실시간 추가.
    • unshift로 최신 로그를 테이블 상단에 추가하며, 메모리 관리를 위해 최대 50개로 제한.
  • 설명:

4. 데이터베이스 변경 감지 (오라클 트리거 추가)
위 코드에서는 간단히 setInterval로 시뮬레이션했지만, 실제로는 오라클에서 데이터 삽입을 감지해 WebSocket으로 전송해야 합니다. 이를 위해 트리거와 DBMS_ALERT를 사용할 수 있습니다.
트리거 작성
CREATE OR REPLACE TRIGGER debug_log_trigger
AFTER INSERT ON debug_log
FOR EACH ROW
DECLARE
BEGIN
  DBMS_ALERT.SIGNAL('DEBUG_LOG_INSERT', 
    :NEW.log_id || '|' || :NEW.log_time || '|' || :NEW.procedure_name || '|' || :NEW.line_number || '|' || :NEW.message);
  COMMIT;
END;
/
서버에서 DBMS_ALERT 수신
server/middleware/websocket.js를 수정해 DBMS_ALERT를 수신하도록 합니다:
import oracledb from 'oracledb';

async function listenForAlerts(io) {
  const connection = await oracledb.getConnection({
    user: 'your_username',
    password: 'your_password',
    connectString: 'your_connect_string'
  });

  await connection.execute(`BEGIN DBMS_ALERT.REGISTER('DEBUG_LOG_INSERT'); END;`);

  (async function waitForAlert() {
    while (true) {
      const result = await connection.execute(
        `BEGIN DBMS_ALERT.WAITONE('DEBUG_LOG_INSERT', :message, :status, 0); END;`,
        { message: { dir: oracledb.BIND_OUT, type: oracledb.STRING, maxSize: 4000 }, status: { dir: oracledb.BIND_OUT, type: oracledb.NUMBER } }
      );
      if (result.outBinds.status === 0) {
        const [logId, logTime, procedureName, lineNumber, message] = result.outBinds.message.split('|');
        io.emit('newLog', { logId: Number(logId), logTime, procedureName, lineNumber: Number(lineNumber), message });
      }
    }
  })();
}
  • 설명: 트리거가 DBMS_ALERT.SIGNAL로 이벤트를 발생시키면, 서버가 DBMS_ALERT.WAITONE으로 이를 수신해 클라이언트에 전송.

5. 실행 및 테스트
  1. Nuxt 실행:
    npm run dev
  2. 브라우저에서 http://localhost:3000 접속.
  3. 오라클에서 데이터 삽입
    INSERT INTO debug_log (procedure_name, line_number, message) VALUES ('TEST_PROC', 10, 'Real-time test');
    COMMIT;
  • 결과: 삽입 즉시 테이블에 새 로그가 표시됨.

6. 주의사항
  • 성능: WebSocket 연결이 많아질 경우 서버 부하 고려.
  • 보안: 실제 배포 시 CORS 및 인증 추가.
  • DBMS_ALERT 한계: 대량 삽입 시 알림 누락 가능성 있음. 대안으로 DBMS_PIPE나 CDC(Change Data Capture) 사용 고려.

 
 

 
 
 

 

 

 nuxt 도 기본포트가 3000 번인데 웹소켓도 3000 이면 이상한거 아니야 ??

 
Nuxt.js의 기본 포트는 3000이고, WebSocket도 동일한 3000번 포트를 사용하면 혼란이나 충돌에 대한 우려가 생길 수 있습니다. 하지만 실제로는 문제가 되지 않는 이유와 그 배경을 설명드리고, 필요 시 대안을 제시하겠습니다.
왜 문제가 되지 않는가?
Nuxt.js와 WebSocket이 동일한 포트(3000)를 사용해도 충돌이 발생하지 않는 이유는 다음과 같습니다:
  1. HTTP와 WebSocket의 프로토콜 차이:
    • Nuxt는 HTTP/HTTPS 프로토콜로 요청을 처리합니다 (예: http://localhost:3000).
    • WebSocket은 별도의 프로토콜(ws:// 또는 wss://)을 사용하며, HTTP 서버와 동일한 포트를 공유할 수 있습니다.
    • WebSocket은 HTTP 핸드셰이크로 시작한 뒤 업그레이드되므로, 동일한 포트에서 두 프로토콜이 공존 가능합니다.
  2. Socket.io와 Nuxt의 통합:
    • 이전 예제에서 socket.io를 Nuxt의 서버(event.node.res.socket.server)에 붙였기 때문에, Nuxt의 H3 서버가 HTTP와 WebSocket 요청을 모두 처리합니다.
    • socket.io는 내부적으로 HTTP 서버를 재사용하며, WebSocket 연결 요청(ws://localhost:3000)을 별도로 감지해 처리합니다.
  3. 포트 충돌 없음:
    • 동일한 포트에서 HTTP와 WebSocket이 동작하는 것은 일반적인 설계 패턴입니다. 예를 들어, 많은 실시간 애플리케이션(채팅 앱 등)이 단일 포트로 HTTP와 WebSocket을 모두 제공합니다.

확인해보기
실제 동작을 확인하려면 이전 WebSocket 예제를 실행했을 때:
  • HTTP 요청: http://localhost:3000 → Nuxt 페이지 렌더링.
  • WebSocket 연결: ws://localhost:3000socket.io가 연결 처리.
브라우저 개발자 도구(Network 탭)에서 WebSocket 연결이 ws://localhost:3000으로 성공적으로 열리는 것을 볼 수 있습니다. 이는 Nuxt의 기본 포트와 충돌 없이 동작함을 보여줍니다.

그래도 걱정된다면? 별도 포트 사용하기
만약 동일 포트 사용이 불안하거나, 명시적으로 WebSocket을 다른 포트(예: 3001)로 분리하고 싶다면 아래와 같이 수정할 수 있습니다.
1. 서버 수정 (server/middleware/websocket.js)
import { Server } from 'socket.io';
import oracledb from 'oracledb';

let io;

export default defineEventHandler(async (event) => {
  if (!io) {
    // 별도의 WebSocket 서버를 3001번 포트로 생성
    io = new Server(3001, {
      cors: { origin: '*' }
    });

    console.log('WebSocket server initialized on port 3001');

    const connection = await oracledb.getConnection({
      user: 'your_username',
      password: 'your_password',
      connectString: 'your_connect_string'
    });

    io.on('connection', (socket) => {
      console.log('Client connected:', socket.id);
      sendInitialData(socket);
      socket.on('disconnect', () => console.log('Client disconnected:', socket.id));
    });

    setInterval(async () => {
      const logs = await fetchLatestLogs(connection);
      io.emit('newLog', logs[logs.length - 1]);
    }, 2000);
  }

  return { status: 'WebSocket running on port 3001' };
});

// sendInitialData와 fetchLatestLogs 함수는 이전과 동일
2. 클라이언트 수정 (pages/index.vue)
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { io } from 'socket.io-client';

const logs = ref([]);
let socket;

onMounted(() => {
  // WebSocket을 3001번 포트로 연결
  socket = io('http://localhost:3001'); // 포트 변경

  socket.on('initialLogs', (initialLogs) => {
    logs.value = initialLogs;
  });

  socket.on('newLog', (newLog) => {
    logs.value.unshift(newLog);
    if (logs.value.length > 50) logs.value.pop();
  });
});

onUnmounted(() => {
  if (socket) socket.disconnect();
});
</script>
3. 실행 확인
  • Nuxt는 3000번 포트에서 실행 (npm run dev).
  • WebSocket 서버는 3001번 포트에서 독립적으로 실행.
  • 브라우저에서 http://localhost:3000 접속 시, WebSocket은 ws://localhost:3001로 연결됨.

어떤 방식을 선택할까?
  1. 기본 포트 (3000) 사용:
    • 장점: 설정 단순, 단일 포트로 관리 편리.
    • 단점: 포트가 겹친다는 느낌 때문에 혼란 가능성.
    • 추천 상황: 소규모 프로젝트, 개발 환경.
  2. 별도 포트 (3001) 사용:
    • 장점: HTTP와 WebSocket의 역할 명확히 분리, 디버깅 용이.
    • 단점: 추가 포트 관리 필요, 방화벽 설정 주의.
    • 추천 상황: 대규모 프로젝트, 배포 환경.

결론
Nuxt와 WebSocket이 동일한 3000번 포트를 사용하는 것은 기술적으로 문제없고, 일반적인 패턴입니다. socket.io가 Nuxt의 HTTP 서버를 재사용하므로 충돌이 발생하지 않습니다. 하지만 직관성과 분리성을 위해 별도 포트를 원한다면 위처럼 수정하면 됩니다. 어떤 방식이 더 적합한지 프로젝트 요구사항에 따라 선택하세요!
추가로 궁금한 점 있으면 언제든 물어보세요!
728x90