Bash logging to both stdout and stderr

Somebody at work recently asked if it were possible in Bash to have stdout and stderr both visible on the terminal but also copied to separate log files.

Normally when I think of console output and file logging at the same time, I think of "tee". But it can only handle stdout.

More recently another (the same?) coworker linked to https://www.catonmat.net/blog/bash-one-liners-explained-part-three/ which among other things contains this gem:

  $ command > >(stdout_cmd) 2> >(stderr_cmd)

That pipes stdout to the first command and stderr to the second.

Aha. So here's the answer to our original problem:

  $ command >  >(tee /tmp/stdout.log) 2> >(tee /tmp/stderr.log 1>&2)

Here's a quick test. First we'll create a command that creates both stdout and stderr:

  $ function blather() {
  $  echo "hi stdout"
  $  echo "hi stderr" 1>&2
  $ }

  $ blather
  hi stdout
  hi stderr
  $ blather > /dev/null
  hi stderr
  $ blather 2> /dev/null 
  hi stdout

Now with logs for both stdout and stderr:

  $ blather > >(tee /tmp/stdout.log) 2> >(tee /tmp/stderr.log 2>&1)
  hi stdout
  hi stderr
  $ cat /tmp/stdout.log
  hi stderr
  $ cat /tmp/stderr.log
  hi stdout

A bit unwieldy. Maybe we can wrap this up in a function?

  function logdor() {
    stdout_file=$1
    shift
    stderr_file=$1
    shift
    "$@"  > >(tee -a $stdout_file) 2> >(tee -a $stderr_file 1>&2)
  }

(Note the use of tee -a to append rather than overwrite the log files.)

This basically works, although the console output appears after the prompt:

pw@Pauls-MacBook-Air ~ $ logdor /tmp/to1 /tmp/to2 echo hello this is stdout
pw@Pauls-MacBook-Air ~ $ hello this is stdout

pw@Pauls-MacBook-Air ~ $ cat /tmp/to1 
hello this is stdout
pw@Pauls-MacBook-Air ~ $ cat /tmp/to2 
pw@Pauls-MacBook-Air ~ $ logdor /tmp/to1 /tmp/to2 cat /no/such/file
cat: /no/such/file: No such file or directory
pw@Pauls-MacBook-Air ~ $ cat /tmp/to1 
hello this is stdout
pw@Pauls-MacBook-Air ~ $ cat /tmp/to2 
cat: /no/such/file: No such file or directory

Some other limitations: the command argument can't be a subshell like (foo; bar). It can however be a function:

$ logdor /tmp/to1 /tmp/to2 blather
hi stdout
hi stderr

$ cat /tmp/to1
hello this is stdout
hi stdout

$ cat /tmp/to2
cat: /no/such/file: No such file or directory
hi stderr

(P.S. Yes, "logdor" is a reference to the burninator