I was sad log.Logger was not an io.Writer....

3,237 views
Skip to first unread message

Nate Finch

unread,
Jan 15, 2013, 1:09:32 PM1/15/13
to golan...@googlegroups.com
I just went to pipe the error output of an os/exec command (which writes to an io.Writer) into my log, and realized that log.Logger is not an io.Writer, and that made me sad. Then I realized I can just do this:

type LogWriter log.Logger

func (w *LogWriter) Write(b []byte) (int, error) {
(*log.Logger)(w).Print(string(b))
return len(b), nil
}

And then I can just pass it in like this:

useWriter((*LogWriter)(logger))

If there's a better way to do this conversion please let me know.... the parens are a little crazy for my taste.

Finally, I'm still a little sad that Logger doesn't just implement Writer by default, but at least the work around is pretty easy.... and also a nice example of how easy it is to extend existing types with custom functions.

Jan Mercl

unread,
Jan 15, 2013, 1:17:24 PM1/15/13
to Nate Finch, golang-nuts
I guess log.Logger doesn't implement io.Writer for a reason. The
wrapper you wrote doesn't guarantee non-intermixed of parts of log
messages (text lines), which log API, I think provides (even though I
cannot find where is that documented, so I may be completely wrong).

-j

Jan Mercl

unread,
Jan 15, 2013, 1:19:19 PM1/15/13
to Nate Finch, golang-nuts
On Tue, Jan 15, 2013 at 7:17 PM, Jan Mercl <0xj...@gmail.com> wrote:
> (even though I
> cannot find where is that documented, so I may be completely wrong).

Here it is: http://golang.org/pkg/log/#Logger

"A Logger represents an active logging object that generates lines of
output to an io.Writer. Each logging operation makes a single call to
the Writer's Write method. A Logger can be used simultaneously from
multiple goroutines; it guarantees to serialize access to the Writer."

-j

Nate Finch

unread,
Jan 15, 2013, 1:36:57 PM1/15/13
to golan...@googlegroups.com, Nate Finch
I guess I don't understand why exposing a Write method would break that functionality, nor why my method breaks it.

minux

unread,
Jan 15, 2013, 1:40:38 PM1/15/13
to Nate Finch, golan...@googlegroups.com
On Wed, Jan 16, 2013 at 2:09 AM, Nate Finch <nate....@gmail.com> wrote:
I just went to pipe the error output of an os/exec command (which writes to an io.Writer) into my log, and realized that log.Logger is not an io.Writer, and that made me sad. Then I realized I can just do this:

type LogWriter log.Logger

func (w *LogWriter) Write(b []byte) (int, error) {
(*log.Logger)(w).Print(string(b))
return len(b), nil
}

And then I can just pass it in like this:

useWriter((*LogWriter)(logger))

If there's a better way to do this conversion please let me know.... the parens are a little crazy for my taste.
use a struct to wrap the pointer to logger so that you don't need to add the parens and starts.
type logWriter struct { *log.Logger }
func (w logWriter) Write(b []byte) (int, error) {
      w.Printf("%s", b)
      return len(b), nil
}

Jan Mercl

unread,
Jan 15, 2013, 1:50:17 PM1/15/13
to Nate Finch, golang-nuts
On Tue, Jan 15, 2013 at 7:36 PM, Nate Finch <nate....@gmail.com> wrote:
> I guess I don't understand why exposing a Write method would break that
> functionality, nor why my method breaks it.

Look here: http://golang.org/src/pkg/log/log.go?s=4418:4472#L120

`Output` is the common "output" sink. It atomically writes a header +
's' into the log. Your wrapper frees clients of the now io.Writer to
concurrently log any _parts_ of the intended message to the log and
even worse - in no particular order. This is now possible:

[time stamp]: Abcde
[time stamp]: 0123
[time stamp]: fghi
[time stamp]: jkl
[time stamp]: 456
...

instead of intended

[time stamp]: Abcdefghijklmnopqrstuvwxyz
[time stamp]: 0123456789

In short, there is generally no semantic granularity available with
w.Write (you mentiond pipes, think of e.g. also buffers and their
effects).

Another example of nondeterministic `Write` lenghts is eg. io.Copy,
also used in package exec.

-j

minux

unread,
Jan 15, 2013, 1:53:27 PM1/15/13
to Nate Finch, golan...@googlegroups.com
On Wed, Jan 16, 2013 at 2:36 AM, Nate Finch <nate....@gmail.com> wrote:
I guess I don't understand why exposing a Write method would break that functionality, nor why my method breaks it.
for example, depending on your buffering, the output of your executed program might
show up all in one log message, or in multiple log messages (and the border might not be
what you'd have expected).

Nate Finch

unread,
Jan 15, 2013, 2:08:54 PM1/15/13
to golan...@googlegroups.com, Nate Finch
Yeah, sorry, I was aware of those caveats, but hadn't really thought it through. I think of Write as a a single atomic thing, but with buffering etc it's not really like that.

OK, dang, I guess I have to do it the hard way and write out to a buffer, and then pass that on to the logger.  That's less fun. :)

Nate Finch

unread,
Jan 15, 2013, 4:40:31 PM1/15/13
to golan...@googlegroups.com, Nate Finch
ok, now I do it this way... more code, but more reliable output:

func run(cmd string, stdout io.Writer, errLog *log.Logger) error {
cmd := exec.Command(cmd)
cmd.Stdout = stdout

reader, err := cmd.StderrPipe()
if err != nil {
return err
}
if err := cmd.Start(); err != nil {
return err
}

if b, err := ioutil.ReadAll(reader); err != nil {
return err
} else {
if len(b) > 0 {
errLog.Printf("%s", b)
}
}
return cmd.Wait()

minux

unread,
Jan 15, 2013, 4:51:48 PM1/15/13
to Nate Finch, golan...@googlegroups.com
On Wed, Jan 16, 2013 at 5:40 AM, Nate Finch <nate....@gmail.com> wrote:
ok, now I do it this way... more code, but more reliable output:

func run(cmd string, stdout io.Writer, errLog *log.Logger) error {
cmd := exec.Command(cmd)
cmd.Stdout = stdout

reader, err := cmd.StderrPipe()
instead of cmd.StderrPipe(), you can just assign a bytes.Buffer to cmd.Stderr,
then you don't need to do ioutil.ReadAll below.

Nate Finch

unread,
Jan 15, 2013, 5:46:34 PM1/15/13
to golan...@googlegroups.com
Ahh, thanks minux. Though it was nice to discover pipes... Certainly a bytes.Buffer is better. I should really just read the std lib front to back.

Nate Finch

unread,
Jan 15, 2013, 9:48:03 PM1/15/13
to golan...@googlegroups.com
Final version (hopefully), much nicer than the ugliness I had before :)

func run(name string, stdout io.Writer, errLog *log.Logger) error {
errOut := bytes.Buffer{}
cmd := exec.Command("go", "run", name)
cmd.Stdout = stdout
cmd.Stderr = &errOut

err := cmd.Run()
if errOut.Len() > 0 {
errLog.Printf("%s", errOut.String())
}
return err

roger peppe

unread,
Jan 16, 2013, 4:59:35 AM1/16/13
to Nate Finch, golang-nuts
On 16 January 2013 02:48, Nate Finch <nate....@gmail.com> wrote:
> err := cmd.Run()
> if errOut.Len() > 0 {
> errLog.Printf("%s", errOut.String())

i'm not sure you want to do this - if the stderr output contains more than
one line, it'll all come out in one log message.

maybe something like this (untested):

for _, s := range strings.Split(strings.TrimRight(errOut.String(),
"\n"), "\n") {
errLog.Output(1, s)
}

as for the general "make a Writer from a log.Logger" problem,
here's an example of a solution. you might use it if you wanted
to see log messages in a timely way (probably not an issue when
using it with the go tool):

http://play.golang.org/p/ruQZM8Bhf-

> On Tuesday, January 15, 2013 5:46:34 PM UTC-5, Nate Finch wrote:
>>
>> Ahh, thanks minux. Though it was nice to discover pipes... Certainly a
>> bytes.Buffer is better. I should really just read the std lib front to back.
>
> --
>
>

Jan Mercl

unread,
Jan 16, 2013, 5:31:26 AM1/16/13
to roger peppe, Nate Finch, golang-nuts
On Wed, Jan 16, 2013 at 10:59 AM, roger peppe <rogp...@gmail.com> wrote:
> as for the general "make a Writer from a log.Logger" problem,
> here's an example of a solution. you might use it if you wanted
> to see log messages in a timely way (probably not an issue when
> using it with the go tool):
>
> http://play.golang.org/p/ruQZM8Bhf-

I suppose you're aware the problem of logging to io.Writer is not
going away by it:

http://play.golang.org/p/lmvPaNDbhi

-j

roger peppe

unread,
Jan 16, 2013, 5:42:44 AM1/16/13
to Jan Mercl, Nate Finch, golang-nuts
i'm sorry, i don't understand your remark or the significance of your code.

if you're trying to say that it won't work if the io.Writer is used
concurrently,
that's fine - io.Writer is not ok for concurrent use unless documented
otherwise.

Nate Finch

unread,
Jan 16, 2013, 7:39:06 AM1/16/13
to golan...@googlegroups.com, Nate Finch
On Wednesday, January 16, 2013 4:59:35 AM UTC-5, rog wrote:
On 16 January 2013 02:48, Nate Finch <nate....@gmail.com> wrote:
> err := cmd.Run()
> if errOut.Len() > 0 {
> errLog.Printf("%s", errOut.String())

i'm not sure you want to do this - if the stderr output contains more than
one line, it'll all come out in one log message.

Actually, I specifically do want the entire output as a single logged message. It's a single nugget of information that occurred at a single point in time.  And in fact, if you split it up into multiple calls to Logger.Print like you have, then you could have other log messages interspersed with this output (from other goroutines), which I think is what Jan is trying to say.
Reply all
Reply to author
Forward
0 new messages