如何捕获特定函数调用的Stdout



我们可以通过利用process.stdout.write来捕获Stdout输出,例如

let output = '';
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
process.stdout.write = (chunk, encoding, callback) => {
  if (typeof chunk === 'string') {
    output += chunk;
  }
  return originalStdoutWrite(chunk, encoding, callback);
};
console.log('foo');
console.log('bar');
console.log('baz');
process.stdout.write = originalStdoutWrite;

这就是实现output-interceptor和类似模块的方式。

但是,这种方法在异步/并发环境中不起作用,例如

const createOutputInterceptor = require('output-interceptor').createOutputInterceptor;
const delay = require('delay');
const interceptOutput = createOutputInterceptor();
const run = async (domain) => {
  await interceptOutput(async () => {
    console.log(domain, 1);
    await delay(Math.random() * 1000);
    console.log(domain, 2);    
  });
  console.log('"%s" domain captured output:', domain, JSON.stringify(interceptOutput.output));
};
run('foo');
run('bar');
run('baz');

正如预期的,这将产生任意结果:

"bar" domain captured output: "bar 1n"
"baz" domain captured output: "baz 1nbar 2n"
"foo" domain captured output: "foo 1n"

有没有在特定回调/承诺执行的上下文中覆盖process.stdout的方法?

我想知道是否有类似于可用于实现这种结果的域类似的魔法。

事实证明,我通过建议使用域来回答自己的问题。

诀窍是覆盖process.stdout.write并检查process.domain。如果可以将process.domain识别为我们为捕获Stdout而创建的域,则将stdout块附加到该域,例如。

const createDomain = require('domain').create;
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
process.stdout.write = (chunk, encoding, callback) => {
  if (
    process.domain &&
    process.domain.outputInterceptor !== undefined &&
    typeof chunk === 'string'
  ) {
    process.domain.outputInterceptor += chunk;
  }
  return originalStdoutWrite(chunk, encoding, callback);
};
const captureStdout = async (routine) => {
  const domain = createDomain();
  domain.outputInterceptor = '';
  await domain.run(() => {
    return routine();
  });
  const output = domain.outputInterceptor;
  domain.outputInterceptor = undefined;
  return output;
};

这是一个完全有效的示例:

const createDomain = require('domain').create;
const delay = require('delay');
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
process.stdout.write = (chunk, encoding, callback) => {
  if (
    process.domain &&
    process.domain.outputInterceptor !== undefined &&
    typeof chunk === 'string'
  ) {
    process.domain.outputInterceptor += chunk;
  }
  return originalStdoutWrite(chunk, encoding, callback);
};
let domainIndex = 0;
const captureStdout = async (routine) => {
  const domain = createDomain();
  domain.outputInterceptor = '';
  await domain.run(() => {
    return routine();
  });
  const output = domain.outputInterceptor;
  domain.outputInterceptor = undefined;
  return output;
};
const run = async (domainName) => {
  process.stdout.write(domainName + ' domain 0n');
  await delay(Math.random() * 100);
  process.stdout.write(domainName + ' domain 1n');
  await delay(Math.random() * 100);
  process.stdout.write(domainName + ' domain 2n');
};
const main = async () => {
  console.log('<><><> 0');
  const result1 = captureStdout(() => {
    return run('foo');
  });
  console.log('<><><> 1');
  const result2 = captureStdout(() => {
    return run('bar');
  });
  console.log('<><><> 2');
  const result3 = captureStdout(() => {
    return run('bar');
  });
  console.log('<><><> 3');  
  await Promise.all([
    result1,
    result2,
    result3,
  ]);
  console.log(
    result1,
    result2,
    result3
  );
};
main();

上面的示例将产生输出:

<><><> 0
foo domain 0
<><><> 1
bar domain 0
<><><> 2
bar domain 0
<><><> 3
foo domain 1
bar domain 1
bar domain 1
foo domain 2
bar domain 2
bar domain 2
Promise { 'foo domain 0nfoo domain 1nfoo domain 2n' } Promise { 'bar domain 0nbar domain 1nbar domain 2n' } Promise { 'bar domain 0nbar domain 1nbar domain 2n' }

我已经更新了output-interceptor模块以使用上述逻辑的变体。

最新更新