Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

node.js에서 표준출력(stdout, stderr) 가로채기

Node.js로 프로그램을 짜던 중 표준출력을 변경해야 할 필요가 있었다. 여기서 표준 출력이라는 것은 stdout이나 stderr로 출력되는 내용을 말하고 기본적으로는 Node.js에서 console로 출력하고 터미널에 메시지가 출력된다. 이는 console.log()를 사용하면 터미널에 출력된다고 생각하기 쉽지만 좀 더 적확히는 console.log()stdout으로 출력되고 console.error()stderr로 출력되는 것인데 stdoutstderr가 터미널로 출력하는 것이다. 물론 일반적으로는 그냥 이 stdout과 stderr을 그대로 사용하고 로거를 따로 사용하거나 shell에서 파이프로 파일에 쓰이도록 하는 것이 더 바람직하다고 본다. 하지만 좀 특별한 용도로 소스내에서 stdout이나 stderr를 가로채서 임의로 조작해야할 필요가 있었다.


__defineGetter__ 사용하기
MDN에 따르면 __defineGetter__는 표준도 아니고 폐기된 API이지만 현재 Node.js에서 사용할 수 있다.(즉, 아직 V8이 지원한다.) 이 특이한 이름의 __defineGetter__는 객체의 프로퍼티를 함수에 바인딩해서 해당 프로퍼티가 조회될 때 바인딩된 함수가 호출되도록 하는데 stdout, stderr를 가로채는 방법을 검색하다 보면 이 방법이 많이 나온다.

fs = require('fs');

console.log('first log message');
console.error('first error message');

var oldStdout = process.stdout,
    oldStderr = process.stderr,
    newStdout = fs.createWriteStream('log.log', {flags: 'a'}),
    newStderr = fs.createWriteStream('err.log', {flags: 'a'});

// stdout, stderr 가로챔
process.__defineGetter__('stdout', function() {
  return newStdout;
});

process.__defineGetter__('stderr', function() {
  return newStderr;
});

console.log('second log message');
console.error('second error message');

// stdout, stderr 복구
process.__defineGetter__('stdout', function() {
  return oldStdout;
});

process.__defineGetter__('stderr', function() {
  return oldStderr;
});

console.log('third log message');
console.error('third error message');

앞부분에서 console.logconsole.error로 메시지를 출력하고 __defineGetter__를 이용해서 process 객체의 stdoutstderr 프로퍼티에 새로운 함수를 바인딩했고 이 함수에서는 위에서 생성한 파일스트림을 각각 리턴하도록 했다. 그리고 마지막에서는 원래의 stdout, stderr로 다시 복구해 주었다.

$ node example.js
first log message
first error message
??third log message
third error message
$ cat log.log
second log message
$ cat err.log
second error message

위의 실행결과에서 보듯이 앞부분에서 출력한 메시지는 js파일 실행시 그대로 출력되었지만 stdout, stderr을 가로챈 뒤에 실행한 뒤에 출력한 console.logconsole.err는 터미널에 출력되는 대신 각각 파일에 출력되었다.


write 함수 오버라이드하기
__defineGetter__를 이용한 방법도 잘 동작하지만 표준이 아닌 __defineGetter__를 사용하는 부분이 약간 찝찝하다. stdoutstderr 에는 실제 메시지를 출력하는 write 함수가 있는데 이 함수가 실제로 메시지를 출력하는 함수이므로 이 함수를 통째로 가로챌 수 있다.

var fs = require('fs');

console.log('first log message');
console.error('first error message');

var oldStdoutWrite = process.stdout.write,
    oldStderrWrite = process.stderr.write;

// stdout, stderr 가로챔
process.stdout.write = function(str, encoding, fg) {
  fs.appendFile('log.log', str, function(err) {});
}
process.stderr.write = function(str, encoding, fg) {
  fs.appendFile('err.log', str, function(err) {});
}

console.log('second log message');
console.error('second error message');

// stdout, stderr 복구
process.stdout.write = oldStdoutWrite;
process.stderr.write = oldStderrWrite;

console.log('third log message');
console.error('third error message');

앞에서 보았던 과정과 비슷하다. 달라진 점은 stdoutstderrwrite함수를 백업한 뒤에 새로운 함수로 덮어쓰고 나중에 원래의 write함수로 복구해 준 것이다. write함수가 스트림이 아니라서 여기서는 pipe대신에 그냥 문자열을 받아서 파일에 쓰도록 했다.

$ node test2.js
first log message
first error message
third log message
third error message
$ cat log.log
second log message
$ cat err.log
second error message

물론 결과는 앞에와 동일하다.
2013/04/21 23:45 2013/04/21 23:45