Profile image

Kim Songhyun

카카오 프론트엔드 개발자 since 2018. 01.

인터넷을 돌아다니다 보면 console.log를 이용해 예술을 하는 사람들을 가끔 만날 수 있다.

쿠팡의 콘솔. 이쪽은 아스키 아트도 추가했다

브라우저 콘솔은 CSS 스타일시트를 지원하기 때문에 조금 번거롭긴 하지만 이런 작품을 만들기 어렵지 않다. 예를 들어 매트릭스 스타일의 녹색 글씨를 찍고 싶다면 이렇게 하면 된다.

1console.log('%cHello, world!', 'color: #a4f644; background-color: #000;');

%c 이후의 글자에는 모두 같은 스타일이 입혀지게 된다. 새로운 스타일을 씌우고 싶으면 %c를 또 쓰면 된다.

FE에서는 전혀 어려운 작업이 아니기 때문에 당연히 BE에서도 CSS까진 아니더라도 비슷한 방법론을 제공한다고 생각했는데, 알고 보니 전혀 아니었다. 생각해보면 당연한 일이다. 터미널에서는 색깔을 변경하기 위해서 ANSI escape code를 사용하기 때문이다. 심지어 이조차도 윈도에서는 10 버전 이후에나 정식으로 지원하기 시작했다.

바퀴를 새로 만드는 데 관심이 없다면 이를 추상화한 구현체를 갖다 쓰면 된다. Node.js 진영에서는 chalk가 사실상 표준 역할을 하고 있다. 최근 주간 다운로드 수가 무려 4400만 건! 부럽다 다만, 라이브러리의 힘을 빌려도 좋지만 생각보다 원리는 간단하므로 직접 만들어보는 것도 좋겠다.

일단 ANSI escape code는 글자색 뿐 아니라 커서의 위치, 굵기, 배경색 등 터미널 전체에서 커서와 텍스트로 할 수 있는 모든 것을 다룰 수 있다. 한 위치에서 프로그레스 바를 계속 그려준다든지 하는 것도 이 이스케이프 코드를 이용해서 작업하는 것이다. 이것도 직접 만들어 보면 재밌겠지만 일단 글자를 자유롭게 포매팅하는 것부터 시작해 보자.

일단 아래 예제를 보자.

1console.log('\x1b[31mHello, world!\x1b[0m');

여러분이 어떤 컬러스킴을 터미널에 적용했느냐에 따라 다르겠지만 빨간 색으로 헬로월드를 찍을 수 있었을 것이다.

이 코드는 크게 세 부분으로 나눌 수 있다.

  1. \x1b[31m
  2. Hello, world!: 실제로 내가 쓰고 싶었던 부분
  3. \x1b[0m

\x1b[로 시작해서 m으로 끝나는 코드가 바로 ANSI escape code이다. \x1b는 16진수 1B, 즉 27인데, 아스키코드표에서 ESC가 27이기 때문에 이 값을 사용한다(escape code라는 이름이 붙은 건 이것 때문인 듯하다). 8진수인 \033을 사용하기도 하는데 이건 나는 개인적으로 16진수로 적는 쪽을 선호한다.

[는 Control Sequence Introducer(CSI)라고 부르는데, 이 뒤에 오는 코드에 따라 커서를 움직인다든지 하는 기능을 수행할 수 있다. 맨 뒤에 오는 m은 Select Graphic Rendition(SGR)이라고 부르고, m 뒤에 오는 글자들에 각종 스타일을 제공한다.

CSI와 SGR 사이에 있는 숫자 310이 색상을 골라 준다. 31은 글자색을 빨간색으로 바꾼다는 뜻이고, 0은 지금까지 있던 설정을 리셋한다는 뜻으로, 더 이상 빨간 글씨가 찍히지 않도록 해 준다. 0은 생략하여 \x1b[m이라고 적을 수 있다.

글자색 말고도 다른 값을 줄 수도 있다. 이를테면 숫자 1은 굵은 글씨, 3은 기울임꼴이며 30~37, 90~97은 글자색, 40~47, 100~107은 배경색을 바꿔준다. 이렇게 상호 배타적인 설정은 세미콜론을 이용해 동시에 적용할 수 있다. 예를 들어 \x1b[1;3;33;94m은 굵게(1) 기울임꼴(3) 노란색 글자(33)에 하늘색 배경(94)라는 뜻이다.

정리하자면, 내가 작성하고 싶은 글자 앞뒤로 이스케이프 코드를 적어주면 Node.js로도 멋진 콘솔창을 만들 수 있다는 것이다. Tagged template literal을 이용해 빨간 글자 로그를 찍는 방법을 소개하며 이번 글을 마치도록 하겠다.

1const red = (strs, ...values) => { // values는 없다고 가정
2 return `\x1b[31m${strs.join('')}\x1b[0m`;
3}
4
5console.log(red`Hello, world!`);