Curt Poem

프론트 엔드 공부와 지식 나눔을 위한 블로그

개발/기타

로그 출력을 깨끗하게 만드는 방법과 대화형 CLI 만들기

Dovelop 2025. 3. 30. 21:19

어떻게 터미널에서 색이 있는 로그를 출력할 수 있을까요? 출처: 위키백과, ANSI 이스케이프 코드

같은 줄에 로그 출력 갱신하기

터미널에서 npm 패키지를 설치할 때 진행률(%)이 같은 줄에서 갱신되는 것을 본 적이 있을 것입니다. 이러한 방식은 진행률 표시, 로딩 상태 출력 등 실시간 정보를 효율적으로 전달하는 데 유용합니다.하지만 자바스크립트의 console 함수들은 기본적으로 줄바꿈이 포함되어 있으며 이전 줄을 갱신하는 방법도 없습니다. 과연 어떤 방식으로 이런 출력을 구현한 것일까요?

 

Node.js에서는 console.log 대신 process.stdout.write()를 사용하여 같은 줄에 출력을 덮어쓸 수 있습니다. 이를 위해 캐리지 리턴 문자(\r)를 활용하여 커서를 줄의 시작으로 이동한 후 새로운 내용을 출력하면 됩니다.

예제 코드

const total = 100;
let count = 0;

const interval = setInterval(() => {
    count++;
    const percentage = (count / total * 100).toFixed(2);
    process.stdout.write(`\r${percentage}% (${count}/${total}) 처리 중...`);

    if (count >= total) {
        clearInterval(interval);
        console.log("\n모든 작업 완료!");
    }
}, 50);

단 캐리지 리턴은 현재 줄의 맨 앞으로 커서를 이동시키고, 해당 줄에서만 덮어씁니다. 뒤쪽에 남아 있는 텍스트는 덮어쓰지 않기 때문에, 긴 텍스트에서는 예상치 못한 출력이 발생할 수 있습니다.

더 많은 커스텀이 가능한 ANSI코드 활용하기

터미널에서 실행되는 많은 CLI 도구들은 진행 상태를 보다 가독성 높게 표시하기 위해 ANSI 이스케이프 코드를 활용합니다. 예를 들어, npm, yarn, pnpm 같은 패키지 매니저는 패키지를 설치할 때 진행률을 색상과 함께 한 줄에서 실시간으로 갱신합니다. 또한, webpack, jest, mocha 같은 빌드 및 테스트 도구들도 진행률, 경고, 오류 메시지를 강조하는 데 ANSI 코드를 사용합니다.

 

ANSI 코드를 활용하면 중요한 정보를 깔끔하게 전달하는 데 도움이 됩니다. 보다 세밀한 제어를 위해 ANSI 이스케이프 코드를 활용하여 커서를 이동시키거나 색상을 적용할 수도 있습니다.

 

터미널에서 실행되는 많은 CLI 도구들은 진행 상태를 보다 가독성 높게 표시하기 위해 ANSI 이스케이프 코드를 활용합니다. 예를 들어, 빌드 프로세스, 테스트 진행률, 다운로드 속도 등을 표시할 때 줄바꿈 없이 실시간으로 갱신되는 출력을 볼 수 있습니다. 이러한 기능은 사용자 경험을 향상시키고, 중요한 정보를 깔끔하게 전달하는 데 도움이 됩니다.

ANSI 코드란?

ANSI 이스케이프 코드(ANSI Escape Code)는 터미널에서 텍스트의 색상, 스타일, 배경 등을 제어하는 데 사용되는 특수한 문자열입니다. 주로 \x1b (또는 \e)로 시작하여 여러 옵션을 포함하는 코드들로 구성됩니다. 이 코드는 터미널이나 콘솔에서만 해석되며, GUI 환경에서는 작동하지 않습니다. 따라서 브라우저의 콘솔창에서는 적용되지 않습니다.

 

터미널에서 출력되는 텍스트의 모양을 커스터마이즈하고 시각적으로 구분할 수 있게 도와주는 중요한 도구입니다. ANSI 코드에는 다양한 스타일과 색상을 설정할 수 있는 명령들이 포함되어 있습니다. 추가적으로 ANSI 코드는 터미널에서 읽고 해석하기 때문에 처음 알려드린 process.stdout.write가 아닌 일반 console.log문으로 작성하여도 잘 작동합니다. 다만 줄바꿈이 발생하기 때문에 커서를 위로 올리는 등의 동작은 올바르지 않을 수 있습니다.

ANSI 코드 종류

ANSI 코드에는 여러 가지 종류가 있으며, 각 코드마다 특정한 동작을 합니다. 대표적인 ANSI 코드들은 텍스트 스타일을 변경하거나, 텍스트 색상을 바꾸거나, 배경색을 설정하는 등의 작업을 할 수 있습니다.

텍스트 색상 변경

  • \x1b[30m ~ \x1b[37m: 텍스트 색상을 변경할 수 있습니다. (30은 검정색, 31은 빨간색, 32는 초록색, 33은 노란색 등)
  • \x1b[38;5;<color_code>m: 256색 모드로 텍스트 색상 변경. (예: \x1b[38;5;82m은 연두색)

배경 색상 변경

  • \x1b[40m ~ \x1b[47m: 텍스트 배경 색상을 변경할 수 있습니다. (40은 검정색 배경, 41은 빨간색 배경 등)

텍스트 스타일 변경

  • \x1b[1m: 텍스트를 굵게(bold) 만듭니다.
  • \x1b[3m: 이탤릭체로 만듭니다.
  • \x1b[4m: 밑줄을 추가합니다.
  • \x1b[9m: 취소선이 있는 텍스트로 만듭니다.

텍스트 초기화

  • \x1b[0m: 텍스트의 스타일과 색상을 초기화합니다. 모든 스타일을 리셋하려면 이 코드를 사용합니다.

커서 제어

  • \x1b[1A: 커서를 한 줄 위로 이동합니다.
  • \x1b[1B: 커서를 한 줄 아래로 이동합니다.
  • \x1b[2K: 현재 줄을 지웁니다.
  • \x1b[H : 커서를 화면의 첫 번째 줄, 첫 번째 열로 이동 (좌상단으로 이동)
  • \x1b[F: 커서를 마지막 줄로 이동 (터미널에 따라 다르게 작동할 수 있음)
  • \x1b[n;0H: 커서를 지정한 행 n으로 이동 (여기서 n은 숫자, 0은 첫 번째 열)

색상 및 스타일 조합

  • \x1b[31;1m: 빨간색으로 굵은 텍스트를 출력합니다. (색상 코드와 스타일 코드를 세미콜론으로 구분하여 조합할 수 있습니다.)

예제 코드 (커서 이동 및 줄 지우기)

const total = 100;
let count = 0;

const interval = setInterval(() => {
  count++;
  const percentage = ((count / total) * 100).toFixed(2);

  const text = `${percentage}% (${count}/${total}) 처리 중...`;
  process.stdout.write(`\x1b[2K\x1b[H\x1b[32m${text}\x1b[0m`);
  // 캐리지 리턴과 조합가능
  // process.stdout.write(`\r\x1b[2K\x1b[32m${text}\x1b[0m`);

  if (count >= total) {
    clearInterval(interval);
    console.log("\n 모든 작업 완료!");
  }
}, 50);

설명

  • \x1b\[1A: 커서를 한 줄 위로 이동하여 기존 출력을 덮어씁니다.
  • \x1b\[2K: 현재 줄을 지워서 깔끔하게 새로운 내용을 출력할 수 있도록 합니다.
  • \x1b\[32m텍스트\\x1b\[0m: 색상을 적용할 수 있습니다 (32m은 초록색).

앞서 설명 드린 캐리지 리턴(\r)이 커서를 가장 앞으로 이동을 함께 쓰는 것과 같은 효과를 낼 수 있습니다.

 

상호작용을 쉽게 만들기

vite 프로젝트 시작 시 여러 프레임워크, 라이브러리 중 원하는 설정을 선택할 수 있는데, inquirerf를 이용하면 쉽게 구현이 가능합니다.

 

다음은 inquirer를 이용하여 vite와 같이 프레임 워크를 선택하는 코드입니다.

import inquirer from "inquirer";

const questions = [
  {
    type: "list",
    name: "framework",
    message: "어떤 프레임워크를 사용할까요?",
    choices: ["React", "Vue", "Svelte", "Solid"],
  },
];

inquirer.prompt(questions).then((answers) => {
  console.log(`선택한 프레임워크: ${answers.framework}`);
});