안녕하세요 Lannstark입니다!

저번 포스트에서는 console이 무엇인지 console의 여러 기능 중 하나인 Logging 기능의 내부 로직을 살펴보았습니다. Logger 추상 연산, Formatter 추상 연산, Print 추상 연산 기억 나시죠?

이번에는 저번 포스팅에 이어, console의 다른 기능들을 살펴보도록 하겠습니다.

console.log 친척들

console.assert(condition, …data)

console.assert는 boolean인 condition에 따라 …data를 출력하는 함수이다. 명세의 로직은 이렇다.

  1. 만약 condition이 true라면 return
  2. “Assertion failed”” 와 같이 condition이 false라는 것을 알리기 위한 specifier 없는 문자열을 message라 하고, data의 길이가 0이라면, messagedata에 넣는다.
  3. data의 길이가 0이 아니라면,
    data[0]을 first라 하고
    first의 type이 String이라면 messagedata의 첫 번째 원소로 집어넣고, 원래 있던 data를 한 칸 씩 뒤로 미룬다.
    String이 아니라면, data[0]을 message + ":" + " " + first로 집어 넣는다.
  4. Logger 추상 연산을 수행한다.
    Logger("assert", data)

* specifier : %s나 %d와 같이 포맷팅에 사용되는 기호

위의 로직대로라면, console.assert(false, "12")console.assert(false, 12)의 출력 결과는 달라야 한다.
console.assert(false, "12")Assertion failed: 12가 되어야 하고, console.assert(false, 12)Aseertion failed 12가 되어야 한다. 하지만 실제로는 모두 Assertion failed: 12라고 출력된다.

크롬

노드

console.assert(false, 12)
// Assertion failed: 12
console.assert(false, "12")
// Assertion failed: 12

이 기능을 보고 혹시 mocha 같은 테스팅 모듈에서 console.assert를 사용했을까 싶어 찾아봤더니 console.assert 대신에 노드의 코어 모듈인 assert를 사용하고 있었다.
많이 사용되는 기능은 아닌 듯 하다.

console.table(tabularData, properties)

tabular이라는 단어의 뜻은 ‘표로 정리된’이라는 뜻이다.

예시를 보며 설명하도록 하자. 아래의 예시는 node를 사용하여 돌렸다.

console.table([{a: 1, b: 2}, {a: 3, b: 4}])
/* 출력 결과
┌─────────┬───┬───┐
│ (index) │ a │ b │
├─────────┼───┼───┤
│    0    │ 1 │ 2 │
│    1    │ 3 │ 4 │
└─────────┴───┴───┘
*/

console.table([{ a: 1, b: 2 }, { a: 3, b: 4 }], ['a'])
/* 출력 결과
┌─────────┬───┐
│ (index) │ a │
├─────────┼───┤
│    0    │ 1 │
│    1    │ 3 │
└─────────┴───┘
*/

tabularData에는 배열이 들어가게 되는데, 배열 안에는 한 row(가로 줄)에 대응되는 객체가 들어 있다. 각 객체의 key는 column의 이름(properties)이고, value는 그에 대응되는 값이다.
properties에는 배열이 들어가는, 배열 안에는 String으로 어떤 property만을 출력할지 명시할 수 있다.

이 함수와 관련하여, pm2가 관리하고 있는 프로세스들을 표로 어떻게 출력해 주는지 궁금해서 찾아봤더니 놀랍게도 pm2console.table()을 사용하고 있지 않았다. 대신 cli-table-redemption라는 모듈을 사용하고 있었다.
왜일까? 성능 문제일까? 기억하고 있다가 나중에 알아봐야 겠다. 링크 - 추가 예정

console.trace(…data)

console.trace()는 사용 시점에서의 stack trace를 출력해준다.
stacktrace를 가져오는 방법은 node.js stack trace 가져오기에서 다루고 있다.

node.js PERL에서 사용한 예제

Trace
    at repl:1:9
    at Script.runInThisContext (vm.js:96:20)
    at REPLServer.defaultEval (repl.js:329:29)
    at bound (domain.js:396:14)
    at REPLServer.runBound [as eval] (domain.js:409:12)
    at REPLServer.onLine (repl.js:642:10)
    at REPLServer.emit (events.js:187:15)
    at REPLServer.EventEmitter.emit (domain.js:442:20)
    at REPLServer.Interface._onLine (readline.js:290:10)
    at REPLServer.Interface._line (readline.js:638:8)

console.dir(item, options)

item으로 넘어온 객체를 generic JS object formatting으로 출력한다. (generic JS object formatting과 optimally useful formatting은 1편에서 다루었다.)

크롬

node에서는 차이점을 느끼지 못하겠다.

console.log(obj) // { a: 1, b: 2}
console.dir(obj) // { a: 1, b: 2}

그래서 찾아보니, node는 console.dir에 들어온 itemutil.inspect()에 넘겨 나온 결과를 출력한다고 한다. util.inspect()는 이 포스트에서 다루고 있다.

옵션은 명세에 언급되지 않은 것으로 미루어 볼 때, JS 엔진마다 다른 듯 하다.

console.dirxml(…data)

명세에는 console.dirxml의 로직을 이렇게 적어 두었다.

  1. 새로운 list를 만들어 finalList라 하자.
  2. data에 있는 원소를 가능하다면 DOM 트리 형식으로 전환한다. 불가능하다면 optimalyy useful formatting으로 전환한다.
  3. 전환된 data의 원소를 finalList에 넣는다.
  4. 2~3 과정을 data 안의 모든 원소에 대해 반복한다.
  5. Logger 연산을 수행한다.
    Logger("dirxml", finalList)

하지만 실제로는 console.log와의 차이점이 없다고 판단된다.

노드는 실제로 docu에 이렇게 적어 두었다.

This method(console.dirxml) calls console.log passing it the arguments received. Please not that this method does not produce any XML formatting.

[해석] 이 메소드는 건너온 매개변수와 함께 console.log를 호출한다. 이 메소드는 그 어떤 XML 형식을 만들어 내지 않는다.

크롬의 경우도,

console.log(document.body)
console.dir(document.body)
console.dirxml(document.body)

을 해보면 console.logconsole.dirxml이 같다는 것을 발견할 수 있다.

Counting 기능

드디어 무언가를 출력하는 기능은 모두 알아보았다. 이제 counting 기능에 대해 알아보자.

console각각의 namespace 객체에는 count_map이라는 map이 존재한다. 이 map은 key가 문자열이고 value가 숫자이며, 초기에는 비어 있다.

count(label)

  1. 만약 count_map[label]이 존재한다면, 그에 해당되는 value를 1만큼 증가시킨다.
  2. 그렇지 않다면, count_map[label]의 값을 1로 만든다.
  3. 그리고 나서, label + “:” + “ “ + count_map[label]을 concat이라 하여 Logger 추상 연산을 수행한다.
    Looger("count", concat)

예시

console.count("apple") // apple: 1
console.count("apple") // apple: 2

countReset(label)

  1. count_map[label]이 존재한다면 count[label]의 값을 0으로 만든다.

console.count는 유용하게 사용될 여지가 많을 것 같다.
가령 성능 최적화를 위해 어떤 메소드가 가장 많이 불리는지 모니터링 하고 싶을 때, 메소드 제일 처음 부분에 console.count(label)을 달아주고 나오는 로그를 ELK를 돌려 예쁘게 볼 수 있지 않을까 싶다. (아직 ELK를 제대로 사용해 본적은 없다.)

Grouping 기능

group이란 Printer를 호출하여 생산되는 일종의 view이다. (여기셔 view라는 단어가identation을 뜻하는 듯 하다.) consolenamespace 객체는 group stack을 가지고 있다. 말 그대로 group이 쌓이는 stack이다.

group(…data)

내부 로직은 이렇다.

  1. 만약 data가 비어있지 않다면, groupLabelFormatter(data)라 한다. 비어있다면, groupLabel은 구현에 따라 group을 표시할 수 있는 label이 된다.
  2. groupLabel을 새로운 group의 label로 정한다.
  3. 선택적으로, environment가 interactive group을 지원한다면, group에 추가적인 조작이 들어갈 수 있다.
  4. Printer 추상 연산을 수행한다. Printer("group", group)
  5. group stackgroup을 넣는다.

groupCollapsed(…data)

group과 아주 유사하다. 차이점은 아래서 설명하도록 하겠다.

groupEnd()

group stack으로부터 group을 pop한다.

clear()

  1. 적절한 group stack을 비운다.
  2. 가능하다면, console을 깨끗이 비운다.

group기능은 설명만 봐서는 감이 잘 오지 않는다. 크롬에서 사용해보자.

console.group("group1")을 쳤더니, console 창 왼쪽에 줄이 하나 생겼다.

console.log("Lannstark")를 쳤더니, 줄 오른쪽에 Lannstark라는 결과가 나왔다.

console.groupEnd()를 쳤더니, 왼쪽 줄이 사라졌다.

그렇다. console.group()은 들여쓰기를 만들어준다. 만약 console.group("group1") 대신 그냥 console.group()을 사용한다면?

위의 명세에서는 구현 로직에 따라 적절한 label을 가진다고 했다. 크롬console.group이라는 label을 사용하나 보다.

group과 groupCollapsed의 차이

그렇다면 console.groupconsole.groupCollapsed의 차이는 무엇일까?

여기까지는 별 차이가 없어보인다.

그런다음 console.log("Lannstark")를 쳤더니, 아무 변화가 없다. 하지만 화살표를 눌러보면

아까 입력했던 내용들을 모두 보인다.

그렇다. 사실 별 차이는 없고 console.group은 화살표를 누르지 않아도 바로 보여주고, console.groupCollapsed는 화살표를 누를 때까지 보여주지 않는다.

Timing 기능

모든 console 객체는 timer table을 가진다. 이 timer table은 string -> time인 map이다.

time(label)

내부 로직은 이렇다.

  1. 만약 timer table에 label이 key로 존재한다면, return (구현에 따라 해당 label로 timer가 시작되었음을 경고할 수도 있다.)
  2. 그렇지 않다면, timer table에 label을 key로, 현재 시각을 value로 하여 저장한다.

timeLog(label, …data)

timeLog는 간단하다. 해당 label을 key로 하는 data를 time table에서 찾아 현재 시간과의 격차를 계산하고 data와 함께 출력한다.

예를 들면 이렇다.

console.time("Apple")
console.timeLog("Apple", "I like apple")
// Apple: 11409.410ms I like apple

timeEnd(label)

timeEnd 역시 간단하다. 해당 label을 key로 하는 data를 다른 로그 없이 출력한다.

timeLog와의 차이점이라면, timeLog는 타이머가 종료되는 것은 아니나, timeEnd는 그 이름에 걸맞게 타이머 자체가 종료된다.

사용 예시

console.time("Banana")
console.timeEnd("Banana")
// Banana: 1109.696ms

마무리

이번 포스팅에서는 console의 다른 기능들을 알아보았다.
grouping 기능은 잘 사용하지 않을 듯 하고, counting 기능과 timing 기능은 코드 최적화를 위한 자료가 필요할 때 간혹 사용할 것 같다.

드디어 node의 console 도큐를 읽을 때가 되었다. ㅎㅎ
아이 신나

관련 포스팅

JS console 탐구 (1)

node에서 스택 트레이스 가져오기

node의 console 객체, node 출력 글자 색 변경하기

JS 문자열 성능, pm2 로깅 성능 분석