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 }