gRPC 스트리밍으로 실시간 로그 전송 구현하기

문제 상황

여러 마이크로서비스에서 발생하는 로그를 중앙 모니터링 시스템으로 실시간 전송해야 했다. 기존에는 REST API로 배치 전송했지만, 장애 발생 시 즉각 대응이 어려웠다.

WebSocket도 고려했으나 로드밸런서 설정이 복잡하고, 이미 서비스 간 통신에 gRPC를 사용 중이라 gRPC 스트리밍으로 통일하기로 결정했다.

gRPC Server Streaming

proto 파일 정의는 간단했다.

service LogService {
  rpc StreamLogs(LogRequest) returns (stream LogEntry) {}
}

message LogRequest {
  string service_name = 1;
  string level = 2;
}

message LogEntry {
  string timestamp = 1;
  string level = 2;
  string message = 3;
}

Node.js 서버 구현:

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

function streamLogs(call) {
  const { service_name, level } = call.request;
  
  const interval = setInterval(() => {
    const log = {
      timestamp: new Date().toISOString(),
      level: level,
      message: `Log from ${service_name}`
    };
    call.write(log);
  }, 1000);
  
  call.on('cancelled', () => {
    clearInterval(interval);
  });
}

클라이언트에서는 stream을 받아 처리한다.

const call = client.streamLogs({ 
  service_name: 'api-server',
  level: 'error' 
});

call.on('data', (log) => {
  console.log(`[${log.timestamp}] ${log.level}: ${log.message}`);
});

call.on('end', () => {
  console.log('Stream ended');
});

결과

HTTP/2 기반이라 멀티플렉싱이 자동으로 처리되고, 백프레셔도 내장되어 있어 안정적이었다. Protobuf 덕분에 JSON 대비 페이로드도 30% 정도 줄었다.

다만 디버깅이 REST보다 까다로워서 grpcurl, grpc-health-probe 같은 도구를 적극 활용했다. 프로덕션 환경에서 3주간 운영 중인데 연결 끊김 없이 잘 동작하고 있다.