github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/container_log_linux.go (about) 1 //+build linux 2 //+build systemd 3 4 package libpod 5 6 import ( 7 "fmt" 8 "io" 9 "math" 10 "strings" 11 "time" 12 13 "github.com/containers/libpod/libpod/logs" 14 journal "github.com/coreos/go-systemd/v22/sdjournal" 15 "github.com/pkg/errors" 16 "github.com/sirupsen/logrus" 17 ) 18 19 const ( 20 // journaldLogOut is the journald priority signifying stdout 21 journaldLogOut = "6" 22 23 // journaldLogErr is the journald priority signifying stderr 24 journaldLogErr = "3" 25 26 // bufLen is the length of the buffer to read from a k8s-file 27 // formatted log line 28 // let's set it as 2k just to be safe if k8s-file format ever changes 29 bufLen = 16384 30 ) 31 32 func (c *Container) readFromJournal(options *logs.LogOptions, logChannel chan *logs.LogLine) error { 33 var config journal.JournalReaderConfig 34 if options.Tail < 0 { 35 config.NumFromTail = math.MaxUint64 36 } else { 37 config.NumFromTail = uint64(options.Tail) 38 } 39 config.Formatter = journalFormatter 40 defaultTime := time.Time{} 41 if options.Since != defaultTime { 42 // coreos/go-systemd/sdjournal doesn't correctly handle requests for data in the future 43 // return nothing instead of falsely printing 44 if time.Now().Before(options.Since) { 45 return nil 46 } 47 config.Since = time.Since(options.Since) 48 } 49 config.Matches = append(config.Matches, journal.Match{ 50 Field: "CONTAINER_ID_FULL", 51 Value: c.ID(), 52 }) 53 options.WaitGroup.Add(1) 54 55 r, err := journal.NewJournalReader(config) 56 if err != nil { 57 return err 58 } 59 if r == nil { 60 return errors.Errorf("journal reader creation failed") 61 } 62 if options.Tail == math.MaxInt64 { 63 r.Rewind() 64 } 65 66 if options.Follow { 67 go func() { 68 follower := FollowBuffer{logChannel} 69 err := r.Follow(nil, follower) 70 if err != nil { 71 logrus.Debugf(err.Error()) 72 } 73 r.Close() 74 options.WaitGroup.Done() 75 return 76 }() 77 return nil 78 } 79 80 go func() { 81 bytes := make([]byte, bufLen) 82 // /me complains about no do-while in go 83 ec, err := r.Read(bytes) 84 for ec != 0 && err == nil { 85 // because we are reusing bytes, we need to make 86 // sure the old data doesn't get into the new line 87 bytestr := string(bytes[:ec]) 88 logLine, err2 := logs.NewLogLine(bytestr) 89 if err2 != nil { 90 logrus.Error(err2) 91 continue 92 } 93 logChannel <- logLine 94 ec, err = r.Read(bytes) 95 } 96 if err != nil && err != io.EOF { 97 logrus.Error(err) 98 } 99 r.Close() 100 options.WaitGroup.Done() 101 }() 102 return nil 103 } 104 105 func journalFormatter(entry *journal.JournalEntry) (string, error) { 106 usec := entry.RealtimeTimestamp 107 tsString := time.Unix(0, int64(usec)*int64(time.Microsecond)).Format(logs.LogTimeFormat) 108 output := fmt.Sprintf("%s ", tsString) 109 priority, ok := entry.Fields["PRIORITY"] 110 if !ok { 111 return "", errors.Errorf("no PRIORITY field present in journal entry") 112 } 113 if priority == journaldLogOut { 114 output += "stdout " 115 } else if priority == journaldLogErr { 116 output += "stderr " 117 } else { 118 return "", errors.Errorf("unexpected PRIORITY field in journal entry") 119 } 120 121 // if CONTAINER_PARTIAL_MESSAGE is defined, the log type is "P" 122 if _, ok := entry.Fields["CONTAINER_PARTIAL_MESSAGE"]; ok { 123 output += fmt.Sprintf("%s ", logs.PartialLogType) 124 } else { 125 output += fmt.Sprintf("%s ", logs.FullLogType) 126 } 127 128 // Finally, append the message 129 msg, ok := entry.Fields["MESSAGE"] 130 if !ok { 131 return "", fmt.Errorf("no MESSAGE field present in journal entry") 132 } 133 output += strings.TrimSpace(msg) 134 return output, nil 135 } 136 137 type FollowBuffer struct { 138 logChannel chan *logs.LogLine 139 } 140 141 func (f FollowBuffer) Write(p []byte) (int, error) { 142 bytestr := string(p) 143 logLine, err := logs.NewLogLine(bytestr) 144 if err != nil { 145 return -1, err 146 } 147 f.logChannel <- logLine 148 return len(p), nil 149 }