output/stdout.go

// Package output contains writers that lambdalog delivers records to. The
// default is stdout; alternative destinations (CloudWatch, file rotation)
// are in sibling files.
//
// See mercemay.top/src/lambdalog/output/.
package output

import (
	"bufio"
	"io"
	"os"
	"sync"
)

// Stdout returns an io.Writer that writes to os.Stdout with a process-wide
// mutex so records do not interleave when multiple goroutines log
// concurrently. The buffered writer is flushed on every record (by the
// caller), so crashes do not lose the last line.
func Stdout() io.Writer {
	return stdoutInstance
}

var stdoutInstance = newSyncWriter(os.Stdout)

type syncWriter struct {
	mu sync.Mutex
	bw *bufio.Writer
}

func newSyncWriter(w io.Writer) *syncWriter {
	return &syncWriter{bw: bufio.NewWriterSize(w, 8*1024)}
}

// Write satisfies io.Writer.
func (s *syncWriter) Write(p []byte) (int, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	n, err := s.bw.Write(p)
	if err != nil {
		return n, err
	}
	if err := s.bw.Flush(); err != nil {
		return n, err
	}
	return n, nil
}

// Flush flushes the underlying buffered writer. Exported for tests and for
// shutdown hooks.
func (s *syncWriter) Flush() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.bw.Flush()
}

// Flush flushes the default Stdout writer.
func Flush() error { return stdoutInstance.Flush() }

// Discard is an io.Writer that drops all input. It is useful as a default
// when lambdalog is used by libraries that do not want to emit their own
// records during tests.
var Discard io.Writer = discardWriter{}

type discardWriter struct{}

// Write satisfies io.Writer by returning len(p), nil.
func (discardWriter) Write(p []byte) (int, error) { return len(p), nil }

// Multi returns a writer that fans w out to each destination. Unlike
// io.MultiWriter, errors from one destination do not short-circuit the
// write to others.
func Multi(ws ...io.Writer) io.Writer { return multiWriter(ws) }

type multiWriter []io.Writer

// Write writes p to each underlying writer and returns the first error.
func (m multiWriter) Write(p []byte) (int, error) {
	var firstErr error
	for _, w := range m {
		if _, err := w.Write(p); err != nil && firstErr == nil {
			firstErr = err
		}
	}
	return len(p), firstErr
}