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  }