summary refs log tree commit diff
path: root/vendor/github.com/rs/zerolog/writer.go
diff options
context:
space:
mode:
authorEmile <git@emile.space>2024-10-25 15:55:50 +0200
committerEmile <git@emile.space>2024-10-25 15:55:50 +0200
commitc90f36e3dd179d2de96f4f5fe38d8dc9a9de6dfe (patch)
tree89e9afb41c5bf76f48cfb09305a2d3db8d302b06 /vendor/github.com/rs/zerolog/writer.go
parent98bbb0f559a8883bc47bae80607dbe326a448e61 (diff)
vendor HEAD main
Diffstat (limited to 'vendor/github.com/rs/zerolog/writer.go')
-rw-r--r--vendor/github.com/rs/zerolog/writer.go346
1 files changed, 346 insertions, 0 deletions
diff --git a/vendor/github.com/rs/zerolog/writer.go b/vendor/github.com/rs/zerolog/writer.go
new file mode 100644
index 0000000..41b394d
--- /dev/null
+++ b/vendor/github.com/rs/zerolog/writer.go
@@ -0,0 +1,346 @@
+package zerolog
+
+import (
+	"bytes"
+	"io"
+	"path"
+	"runtime"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+// LevelWriter defines as interface a writer may implement in order
+// to receive level information with payload.
+type LevelWriter interface {
+	io.Writer
+	WriteLevel(level Level, p []byte) (n int, err error)
+}
+
+// LevelWriterAdapter adapts an io.Writer to support the LevelWriter interface.
+type LevelWriterAdapter struct {
+	io.Writer
+}
+
+// WriteLevel simply writes everything to the adapted writer, ignoring the level.
+func (lw LevelWriterAdapter) WriteLevel(l Level, p []byte) (n int, err error) {
+	return lw.Write(p)
+}
+
+// Call the underlying writer's Close method if it is an io.Closer. Otherwise
+// does nothing.
+func (lw LevelWriterAdapter) Close() error {
+	if closer, ok := lw.Writer.(io.Closer); ok {
+		return closer.Close()
+	}
+	return nil
+}
+
+type syncWriter struct {
+	mu sync.Mutex
+	lw LevelWriter
+}
+
+// SyncWriter wraps w so that each call to Write is synchronized with a mutex.
+// This syncer can be used to wrap the call to writer's Write method if it is
+// not thread safe. Note that you do not need this wrapper for os.File Write
+// operations on POSIX and Windows systems as they are already thread-safe.
+func SyncWriter(w io.Writer) io.Writer {
+	if lw, ok := w.(LevelWriter); ok {
+		return &syncWriter{lw: lw}
+	}
+	return &syncWriter{lw: LevelWriterAdapter{w}}
+}
+
+// Write implements the io.Writer interface.
+func (s *syncWriter) Write(p []byte) (n int, err error) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	return s.lw.Write(p)
+}
+
+// WriteLevel implements the LevelWriter interface.
+func (s *syncWriter) WriteLevel(l Level, p []byte) (n int, err error) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	return s.lw.WriteLevel(l, p)
+}
+
+func (s *syncWriter) Close() error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if closer, ok := s.lw.(io.Closer); ok {
+		return closer.Close()
+	}
+	return nil
+}
+
+type multiLevelWriter struct {
+	writers []LevelWriter
+}
+
+func (t multiLevelWriter) Write(p []byte) (n int, err error) {
+	for _, w := range t.writers {
+		if _n, _err := w.Write(p); err == nil {
+			n = _n
+			if _err != nil {
+				err = _err
+			} else if _n != len(p) {
+				err = io.ErrShortWrite
+			}
+		}
+	}
+	return n, err
+}
+
+func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) {
+	for _, w := range t.writers {
+		if _n, _err := w.WriteLevel(l, p); err == nil {
+			n = _n
+			if _err != nil {
+				err = _err
+			} else if _n != len(p) {
+				err = io.ErrShortWrite
+			}
+		}
+	}
+	return n, err
+}
+
+// Calls close on all the underlying writers that are io.Closers. If any of the
+// Close methods return an error, the remainder of the closers are not closed
+// and the error is returned.
+func (t multiLevelWriter) Close() error {
+	for _, w := range t.writers {
+		if closer, ok := w.(io.Closer); ok {
+			if err := closer.Close(); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+// MultiLevelWriter creates a writer that duplicates its writes to all the
+// provided writers, similar to the Unix tee(1) command. If some writers
+// implement LevelWriter, their WriteLevel method will be used instead of Write.
+func MultiLevelWriter(writers ...io.Writer) LevelWriter {
+	lwriters := make([]LevelWriter, 0, len(writers))
+	for _, w := range writers {
+		if lw, ok := w.(LevelWriter); ok {
+			lwriters = append(lwriters, lw)
+		} else {
+			lwriters = append(lwriters, LevelWriterAdapter{w})
+		}
+	}
+	return multiLevelWriter{lwriters}
+}
+
+// TestingLog is the logging interface of testing.TB.
+type TestingLog interface {
+	Log(args ...interface{})
+	Logf(format string, args ...interface{})
+	Helper()
+}
+
+// TestWriter is a writer that writes to testing.TB.
+type TestWriter struct {
+	T TestingLog
+
+	// Frame skips caller frames to capture the original file and line numbers.
+	Frame int
+}
+
+// NewTestWriter creates a writer that logs to the testing.TB.
+func NewTestWriter(t TestingLog) TestWriter {
+	return TestWriter{T: t}
+}
+
+// Write to testing.TB.
+func (t TestWriter) Write(p []byte) (n int, err error) {
+	t.T.Helper()
+
+	n = len(p)
+
+	// Strip trailing newline because t.Log always adds one.
+	p = bytes.TrimRight(p, "\n")
+
+	// Try to correct the log file and line number to the caller.
+	if t.Frame > 0 {
+		_, origFile, origLine, _ := runtime.Caller(1)
+		_, frameFile, frameLine, ok := runtime.Caller(1 + t.Frame)
+		if ok {
+			erase := strings.Repeat("\b", len(path.Base(origFile))+len(strconv.Itoa(origLine))+3)
+			t.T.Logf("%s%s:%d: %s", erase, path.Base(frameFile), frameLine, p)
+			return n, err
+		}
+	}
+	t.T.Log(string(p))
+
+	return n, err
+}
+
+// ConsoleTestWriter creates an option that correctly sets the file frame depth for testing.TB log.
+func ConsoleTestWriter(t TestingLog) func(w *ConsoleWriter) {
+	return func(w *ConsoleWriter) {
+		w.Out = TestWriter{T: t, Frame: 6}
+	}
+}
+
+// FilteredLevelWriter writes only logs at Level or above to Writer.
+//
+// It should be used only in combination with MultiLevelWriter when you
+// want to write to multiple destinations at different levels. Otherwise
+// you should just set the level on the logger and filter events early.
+// When using MultiLevelWriter then you set the level on the logger to
+// the lowest of the levels you use for writers.
+type FilteredLevelWriter struct {
+	Writer LevelWriter
+	Level  Level
+}
+
+// Write writes to the underlying Writer.
+func (w *FilteredLevelWriter) Write(p []byte) (int, error) {
+	return w.Writer.Write(p)
+}
+
+// WriteLevel calls WriteLevel of the underlying Writer only if the level is equal
+// or above the Level.
+func (w *FilteredLevelWriter) WriteLevel(level Level, p []byte) (int, error) {
+	if level >= w.Level {
+		return w.Writer.WriteLevel(level, p)
+	}
+	return len(p), nil
+}
+
+var triggerWriterPool = &sync.Pool{
+	New: func() interface{} {
+		return bytes.NewBuffer(make([]byte, 0, 1024))
+	},
+}
+
+// TriggerLevelWriter buffers log lines at the ConditionalLevel or below
+// until a trigger level (or higher) line is emitted. Log lines with level
+// higher than ConditionalLevel are always written out to the destination
+// writer. If trigger never happens, buffered log lines are never written out.
+//
+// It can be used to configure "log level per request".
+type TriggerLevelWriter struct {
+	// Destination writer. If LevelWriter is provided (usually), its WriteLevel is used
+	// instead of Write.
+	io.Writer
+
+	// ConditionalLevel is the level (and below) at which lines are buffered until
+	// a trigger level (or higher) line is emitted. Usually this is set to DebugLevel.
+	ConditionalLevel Level
+
+	// TriggerLevel is the lowest level that triggers the sending of the conditional
+	// level lines. Usually this is set to ErrorLevel.
+	TriggerLevel Level
+
+	buf       *bytes.Buffer
+	triggered bool
+	mu        sync.Mutex
+}
+
+func (w *TriggerLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	// At first trigger level or above log line, we flush the buffer and change the
+	// trigger state to triggered.
+	if !w.triggered && l >= w.TriggerLevel {
+		err := w.trigger()
+		if err != nil {
+			return 0, err
+		}
+	}
+
+	// Unless triggered, we buffer everything at and below ConditionalLevel.
+	if !w.triggered && l <= w.ConditionalLevel {
+		if w.buf == nil {
+			w.buf = triggerWriterPool.Get().(*bytes.Buffer)
+		}
+
+		// We prefix each log line with a byte with the level.
+		// Hopefully we will never have a level value which equals a newline
+		// (which could interfere with reconstruction of log lines in the trigger method).
+		w.buf.WriteByte(byte(l))
+		w.buf.Write(p)
+		return len(p), nil
+	}
+
+	// Anything above ConditionalLevel is always passed through.
+	// Once triggered, everything is passed through.
+	if lw, ok := w.Writer.(LevelWriter); ok {
+		return lw.WriteLevel(l, p)
+	}
+	return w.Write(p)
+}
+
+// trigger expects lock to be held.
+func (w *TriggerLevelWriter) trigger() error {
+	if w.triggered {
+		return nil
+	}
+	w.triggered = true
+
+	if w.buf == nil {
+		return nil
+	}
+
+	p := w.buf.Bytes()
+	for len(p) > 0 {
+		// We do not use bufio.Scanner here because we already have full buffer
+		// in the memory and we do not want extra copying from the buffer to
+		// scanner's token slice, nor we want to hit scanner's token size limit,
+		// and we also want to preserve newlines.
+		i := bytes.IndexByte(p, '\n')
+		line := p[0 : i+1]
+		p = p[i+1:]
+		// We prefixed each log line with a byte with the level.
+		level := Level(line[0])
+		line = line[1:]
+		var err error
+		if lw, ok := w.Writer.(LevelWriter); ok {
+			_, err = lw.WriteLevel(level, line)
+		} else {
+			_, err = w.Write(line)
+		}
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// Trigger forces flushing the buffer and change the trigger state to
+// triggered, if the writer has not already been triggered before.
+func (w *TriggerLevelWriter) Trigger() error {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	return w.trigger()
+}
+
+// Close closes the writer and returns the buffer to the pool.
+func (w *TriggerLevelWriter) Close() error {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+
+	if w.buf == nil {
+		return nil
+	}
+
+	// We return the buffer only if it has not grown above the limit.
+	// This prevents accumulation of large buffers in the pool just
+	// because occasionally a large buffer might be needed.
+	if w.buf.Cap() <= TriggerLevelWriterBufferReuseLimit {
+		w.buf.Reset()
+		triggerWriterPool.Put(w.buf)
+	}
+	w.buf = nil
+
+	return nil
+}