33

I've created:

var access = fs.createWriteStream('/var/log/node/api.access.log', { flags: 'w' });

Then piped:

process.stdout.pipe(access);

Then tried:

console.log("test");

And nothing has appeared in /var/log/node/api.access.log. However this way is working:

process.stdout.pipe(access).write('test');

Could someone explain what am I doing wrong ?

Supervision
  • 1,683
  • 1
  • 18
  • 23

6 Answers6

56

I solved this problem the following way:

var access = fs.createWriteStream('/var/log/node/api.access.log');
process.stdout.write = process.stderr.write = access.write.bind(access);

Of course you can also separate stdout and stderr if you want.

I also would strongly recommend to handle uncaught exceptions:

process.on('uncaughtException', function(err) {
  console.error((err && err.stack) ? err.stack : err);
});

This will cover the following situations:

  • process.stdout.write
  • process.stderr.write
  • console.log
  • console.dir
  • console.error
  • someStream.pipe(process.stdout);
  • throw new Error('Crash');
  • throw 'never do this';
  • throw undefined;
Anatol - user3173842
  • 1,385
  • 1
  • 12
  • 16
11

Checkout console.Console, the parent class of the normal console.

var myLogFileStream = fs.createWriteStream(pathToMyLogFile);

var myConsole = new console.Console(myLogFileStream, myLogFileStream);

You can then you use myConsole.log, myConsole.error, myConsole.dir, etc. and write directly to your file.

You can also monkey patch process.stdout.write as follows:

var fn = process.stdout.write;

function write() {
  fn.apply(process.stdout, arguments);
  myLogFileStream.write.apply(myLogFileStream, arguments);
}

process.stdout.write = write;

there are also other options for overwriting console._stdout depending on the motivation for logging the stdout to a file.

richardpringle
  • 796
  • 5
  • 22
4

process.stdout is a Writable. pipe is a method of Readable(Cf StreamAPI documentation : https://nodejs.org/api/stream.html

You can see the documentation of process.stdout here : https://nodejs.org/api/process.html#process_process_stdout

It's surprising that you can do process.stdout.pipe(...); without any error. But i suppose this call just do nothing. Except returning a new Writable stream binded to stdout (or maybe it returns process.stdout itself. There's no specification for that in the documentation).

If you want to redirect stdout to a file, you have many solutions :

  • Just use your command line to do that. Windows style : node myfile.js > api.access.log.
  • Replace the console object by your own object. And you can rewrite console methods.
  • I'm not sure, but it may be possible to replace process.stdout with your own stream (and you can do whatever you want with this)
Magus
  • 14,796
  • 3
  • 36
  • 51
  • Thanks. Your answer is helpful. For now i decided to rewrite console.log method, but hope to find more constructive solution. – Supervision Sep 23 '15 at 08:29
  • yeah I am really surprised that you can call pipe on a writable without an error, what is up with that? – Alexander Mills Apr 16 '16 at 03:04
  • 2
    According to nodejs [official doc](https://nodejs.org/api/process.html#process_process_stdout) process.stdout is a duplex stream – Alex Jul 18 '17 at 06:52
  • 1
    @Alex it's not so simple: "It is a net.Socket (which is a Duplex stream) unless fd 1 refers to a file, in which case it is a Writable stream." – Andy Nov 15 '18 at 15:12
  • "When Node.js detects that it is being run with a text terminal ("TTY") attached, [`process.stdin`](https://nodejs.org/api/process.html#process_process_stdin) will, by default, be initialized as an instance of `tty.ReadStream` and both [`process.stdout`](https://nodejs.org/api/process.html#process_process_stdout) and [`process.stderr`](https://nodejs.org/api/process.html#process_process_stderr) will, by default be instances of `tty.WriteStream`." —[TTY | Node.js Documentation](https://nodejs.org/api/tty.html#tty_tty) – ma11hew28 Jan 23 '21 at 15:02
  • Just wanted to add that child process takes (pipes in) its readableStream argument as process.stdout (i.e. parent.stdout) which might seem a bit confusing but this relevant relationship parent to child makes writable to be accepted as readable tho ! – projektorius96 Nov 15 '22 at 23:25
2

@user3173842 for the reply on I solved this problem the following way:

var access = fs.createWriteStream('/var/log/node/api.access.log');
process.stdout.write = process.stderr.write = access.write.bind(access);

you do understand that process.stdout continues after process.on('exit') and therefore the fs.WriteStream closes after with process.stdout, according to https://github.com/nodejs/node/issues/7606

so now the question remains, if the developer desired to have the fs.Writestream.write() return to its normal functionality and when fs.Writestream.end is called the writestream closes. How would the developer go about doing this I did

a_l = asyncify_listener
p_std_stream_m is a process stream manager object

p_std_stream_m.std_info.p_stdout_write = process.stdout.write   

process.stdout.write = w_stream.write.bind(w_stream)

process.once('beforeExit', a_l(   p_std_stream_m.handler,process.stdout,w_stream   )   )       

     where in the 'beforeExit' event listener I did
process.stdout.write = p_std_stream_m.std_info.p_stdout_write

w_stream.end()

It works and you use the once method because the process.stdout seems to do a lot of work at this time. Is this good practice, would you do this or what would you do in this situation anyone can feel free to reply.

2

Originally based on @Anatol-user3173842 answer

But in my case I needed to hook the stdout & stderr and also write into a file.

So for those who need to keep the normal stdout behaviour in addition to writing into the file. Use the following.

For non-errors:

// stdout logging hook
const stdoutWrite0 = process.stdout.write;
process.stdout.write = (args) => { // On stdout write
  CustomLogger.writeToLogFile('log', args); // Write to local log file
  args = Array.isArray(args) ? args : [args]; // Pass only as array to prevent internal TypeError for arguments
  return stdoutWrite0.apply(process.stdout, args);
};

For errors:

// stderr logging hook
const stderrWrite0 = process.stderr.write;
process.stderr.write = (args) => { // On stderr write
  CustomLogger.writeToLogFile('error', args); // Write to local error file
  args = Array.isArray(args) ? args : [args]; // Pass only as array to prevent internal TypeError for arguments
  return stderrWrite0.apply(process.stderr, args);
};

// uncaught exceptions
process.on('uncaughtException', (err) => {
  CustomLogger.writeToLogFile('error', ((err && err.stack) ? err.stack : err));
});

Here is the CustomLogger code, where I also separate the log files by date:

export class CustomLogger {

 static LOGS_DIR = 'location-of-my-log-files';

 private static logDailyName(prefix: string): string {
   const date = new Date().toLocaleDateString().replace(/\//g, '_');
   return `${CustomLogger.LOGS_DIR}/${prefix}_${date}.log`;
 }

 private static writeToLogFile(prefix, originalMsg) {
   const timestamp   = Date.now();
   const fileName    = this.logDailyName(prefix);
   const logMsg      = prepareForLogFile(originalMsg);
   fs.appendFileSync(fileName, `${timestamp}\t${logMsg}\n\n`);
   return originalMsg;
 }
}
Nikita Kurtin
  • 5,889
  • 4
  • 44
  • 48
0

Here's a quick example of a logger class that redirects stdout, stderr and exceptions to a file, while still writting everything to the console:

class Logger {
  #log_stream
  #stdout_write
  #stderr_write
  constructor(path) {
    this.#log_stream = fs.createWriteStream(path, { flags: 'a' })
    this.#stdout_write = process.stdout.write.bind(process.stdout)
    this.#stderr_write = process.stderr.write.bind(process.stderr)
    process.stdout.write = this.stdout_write.bind(this)
    process.stderr.write = this.stderr_write.bind(this)
    process.on('uncaughtException', function(err) {
      console.error((err && err.stack) ? err.stack : err)
    })
  }
  stdout_write(buffer) {
    this.#log_stream.write(buffer)
    this.#stdout_write(buffer)
  }
  stderr_write(buffer) {
    this.#log_stream.write(buffer)
    this.#stderr_write(buffer)
  }
}

const logger = new Logger('example.log')
alex.dev
  • 451
  • 1
  • 3
  • 11