github.com/TBD54566975/ftl@v0.219.0/internal/log/json.go (about) 1 package log 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "errors" 7 "io" 8 "time" 9 ) 10 11 var _ Sink = (*jsonSink)(nil) 12 13 type jsonEntry struct { 14 Entry 15 Time string `json:"time,omitempty"` 16 Error string `json:"error,omitempty"` 17 } 18 19 func newJSONSink(w io.Writer) *jsonSink { 20 return &jsonSink{ 21 w: w, 22 enc: json.NewEncoder(w), 23 } 24 } 25 26 type jsonSink struct { 27 w io.Writer 28 enc *json.Encoder 29 } 30 31 func (j *jsonSink) Log(entry Entry) error { 32 var errStr string 33 if entry.Error != nil { 34 errStr = entry.Error.Error() 35 } 36 jentry := jsonEntry{ 37 Time: entry.Time.Format(time.RFC3339Nano), 38 Error: errStr, 39 Entry: entry, 40 } 41 return j.enc.Encode(jentry) 42 } 43 44 // JSONStreamer reads a stream of JSON log entries from r and logs them to log. 45 // 46 // If a line of JSON is invalid an entry is created at the defaultLevel. 47 func JSONStreamer(r io.Reader, log *Logger, defaultLevel Level) error { 48 scan := bufio.NewScanner(r) 49 scan.Buffer(nil, 1024*1024) // 1MB buffer 50 for scan.Scan() { 51 var entry jsonEntry 52 line := scan.Bytes() 53 err := json.Unmarshal(line, &entry) 54 if err != nil { 55 if len(line) > 0 && line[0] == '{' { 56 log.Warnf("Invalid JSON log entry: %s", err) 57 log.Warnf("Entry: %s", line) 58 } 59 log.Log(Entry{Level: defaultLevel, Time: time.Now(), Message: string(line)}) 60 } else { 61 if entry.Error != "" { 62 entry.Entry.Error = errors.New(entry.Error) 63 } 64 entry.Entry.Time, err = time.Parse(time.RFC3339Nano, entry.Time) 65 if err != nil { 66 entry.Entry.Time = time.Now() 67 } 68 log.Log(entry.Entry) 69 } 70 } 71 err := scan.Err() 72 if errors.Is(err, io.EOF) { 73 return nil 74 } 75 return err 76 }