简体   繁体   中英

How to capture a stdout of a specific function invocation?

We can capture stdout output by tapping into the process.stdout.write , eg

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;

This is how the output-interceptor and similar modules are implemented.

However, this approach does not work in a asynchronous/ concurrent environment, eg

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');

As expected, this would produce arbitrary result:

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

Is there a way to override process.stdout only in a context of a specific callback/ Promise execution?

I wondering if there is magic similar to Domain that could be used to achieve such result.

Turns out I have answered my own question by suggesting to use Domain .

The trick is to override process.stdout.write and check for process.domain . If process.domain can be recognised as a domain that we have created with intent to capture the stdout, then we attach the stdout chunk to that domain, eg

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;
};

Here is a fully working example:

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 0\n');

  await delay(Math.random() * 100);

  process.stdout.write(domainName + ' domain 1\n');

  await delay(Math.random() * 100);

  process.stdout.write(domainName + ' domain 2\n');
};

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();

The above example will produce output:

<><><> 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 0\nfoo domain 1\nfoo domain 2\n' } Promise { 'bar domain 0\nbar domain 1\nbar domain 2\n' } Promise { 'bar domain 0\nbar domain 1\nbar domain 2\n' }

I have since updated output-interceptor module to use a variation of the above logic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM