github.com/TBD54566975/ftl@v0.219.0/internal/log/logger.go (about)

     1  package log
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"runtime"
     9  	"time"
    10  
    11  	"golang.org/x/exp/maps"
    12  )
    13  
    14  var _ Interface = (*Logger)(nil)
    15  
    16  const scopeKey = "scope"
    17  
    18  type Entry struct {
    19  	Time       time.Time         `json:"-"`
    20  	Level      Level             `json:"level"`
    21  	Attributes map[string]string `json:"attributes,omitempty"`
    22  	Message    string            `json:"message"`
    23  
    24  	Error error `json:"-"`
    25  }
    26  
    27  // Logger is the concrete logger.
    28  type Logger struct {
    29  	level      Level
    30  	attributes map[string]string
    31  	sink       Sink
    32  }
    33  
    34  // New returns a new logger.
    35  func New(level Level, sink Sink) *Logger {
    36  	return &Logger{
    37  		level:      level,
    38  		attributes: map[string]string{},
    39  		sink:       sink,
    40  	}
    41  }
    42  
    43  func (l Logger) Scope(scope string) *Logger {
    44  	return l.Attrs(map[string]string{scopeKey: scope})
    45  }
    46  
    47  // Attrs creates a new logger with the given attributes.
    48  func (l Logger) Attrs(attributes map[string]string) *Logger {
    49  	attr := make(map[string]string, len(l.attributes)+len(attributes))
    50  	maps.Copy(attr, l.attributes)
    51  	maps.Copy(attr, attributes)
    52  	l.attributes = attr
    53  	return &l
    54  }
    55  
    56  func (l Logger) AddSink(sink Sink) *Logger {
    57  	l.sink = Tee(l.sink, sink)
    58  	return &l
    59  }
    60  
    61  func (l Logger) Level(level Level) *Logger {
    62  	l.level = level
    63  	return &l
    64  }
    65  
    66  func (l Logger) GetLevel() Level {
    67  	return l.level
    68  }
    69  
    70  func (l *Logger) Log(entry Entry) {
    71  	if entry.Level < l.level {
    72  		return
    73  	}
    74  	if entry.Time.IsZero() {
    75  		entry.Time = time.Now()
    76  	}
    77  	entry.Attributes = l.attributes
    78  	if err := l.sink.Log(entry); err != nil {
    79  		fmt.Fprintf(os.Stderr, "ftl:log: failed to log entry: %v", err)
    80  	}
    81  }
    82  
    83  func (l *Logger) Logf(level Level, format string, args ...interface{}) {
    84  	l.Log(Entry{Level: level, Message: fmt.Sprintf(format, args...)})
    85  }
    86  
    87  func (l *Logger) Tracef(format string, args ...interface{}) {
    88  	l.Log(Entry{Level: Trace, Message: fmt.Sprintf(format, args...)})
    89  }
    90  
    91  func (l *Logger) Debugf(format string, args ...interface{}) {
    92  	l.Log(Entry{Level: Debug, Message: fmt.Sprintf(format, args...)})
    93  }
    94  
    95  func (l *Logger) Infof(format string, args ...interface{}) {
    96  	l.Log(Entry{Level: Info, Message: fmt.Sprintf(format, args...)})
    97  }
    98  
    99  func (l *Logger) Warnf(format string, args ...interface{}) {
   100  	l.Log(Entry{Level: Warn, Message: fmt.Sprintf(format, args...)})
   101  }
   102  
   103  func (l *Logger) Errorf(err error, format string, args ...interface{}) {
   104  	if err == nil {
   105  		return
   106  	}
   107  	l.Log(Entry{Level: Error, Message: fmt.Sprintf(format, args...) + ": " + err.Error(), Error: err})
   108  }
   109  
   110  // WriterAt returns a writer that logs each line at the given level.
   111  func (l *Logger) WriterAt(level Level) *io.PipeWriter {
   112  	// Based on MIT licensed Logrus https://github.com/sirupsen/logrus/blob/bdc0db8ead3853c56b7cd1ac2ba4e11b47d7da6b/writer.go#L27
   113  	reader, writer := io.Pipe()
   114  	var printFunc func(format string, args ...interface{})
   115  
   116  	switch level {
   117  	case Trace:
   118  		printFunc = l.Tracef
   119  	case Debug:
   120  		printFunc = l.Debugf
   121  	case Info:
   122  		printFunc = l.Infof
   123  	case Warn:
   124  		printFunc = l.Warnf
   125  	case Error:
   126  		printFunc = func(format string, args ...interface{}) {
   127  			l.Errorf(nil, format, args...)
   128  		}
   129  	default:
   130  		panic(level)
   131  	}
   132  
   133  	go l.writerScanner(reader, printFunc)
   134  	runtime.SetFinalizer(writer, writerFinalizer)
   135  
   136  	return writer
   137  }
   138  
   139  func (l *Logger) writerScanner(reader *io.PipeReader, printFunc func(format string, args ...interface{})) {
   140  	scanner := bufio.NewScanner(reader)
   141  	scanner.Buffer(nil, 1024*1024) // 1MB buffer
   142  	for scanner.Scan() {
   143  		printFunc("%s", scanner.Text())
   144  	}
   145  	if err := scanner.Err(); err != nil {
   146  		l.Errorf(err, "Error while reading from Writer")
   147  	}
   148  	reader.Close()
   149  }
   150  
   151  func writerFinalizer(writer *io.PipeWriter) {
   152  	writer.Close()
   153  }