github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/libpod/logs/log.go (about) 1 package logs 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/containers/podman/v2/libpod/logs/reversereader" 12 "github.com/hpcloud/tail" 13 "github.com/pkg/errors" 14 "github.com/sirupsen/logrus" 15 ) 16 17 const ( 18 // LogTimeFormat is the time format used in the log. 19 // It is a modified version of RFC3339Nano that guarantees trailing 20 // zeroes are not trimmed, taken from 21 // https://github.com/golang/go/issues/19635 22 LogTimeFormat = "2006-01-02T15:04:05.000000000Z07:00" 23 24 // PartialLogType signifies a log line that exceeded the buffer 25 // length and needed to spill into a new line 26 PartialLogType = "P" 27 28 // FullLogType signifies a log line is full 29 FullLogType = "F" 30 ) 31 32 // LogOptions is the options you can use for logs 33 type LogOptions struct { 34 Details bool 35 Follow bool 36 Since time.Time 37 Tail int64 38 Timestamps bool 39 Multi bool 40 WaitGroup *sync.WaitGroup 41 UseName bool 42 } 43 44 // LogLine describes the information for each line of a log 45 type LogLine struct { 46 Device string 47 ParseLogType string 48 Time time.Time 49 Msg string 50 CID string 51 CName string 52 } 53 54 // GetLogFile returns an hp tail for a container given options 55 func GetLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error) { 56 var ( 57 whence int 58 err error 59 logTail []*LogLine 60 ) 61 // whence 0=origin, 2=end 62 if options.Tail >= 0 { 63 whence = 2 64 } 65 if options.Tail > 0 { 66 logTail, err = getTailLog(path, int(options.Tail)) 67 if err != nil { 68 return nil, nil, err 69 } 70 } 71 seek := tail.SeekInfo{ 72 Offset: 0, 73 Whence: whence, 74 } 75 76 t, err := tail.TailFile(path, tail.Config{MustExist: true, Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger}) 77 return t, logTail, err 78 } 79 80 func getTailLog(path string, tail int) ([]*LogLine, error) { 81 var ( 82 nlls []*LogLine 83 nllCounter int 84 leftover string 85 partial string 86 tailLog []*LogLine 87 ) 88 f, err := os.Open(path) 89 if err != nil { 90 return nil, err 91 } 92 rr, err := reversereader.NewReverseReader(f) 93 if err != nil { 94 return nil, err 95 } 96 97 inputs := make(chan []string) 98 go func() { 99 for { 100 s, err := rr.Read() 101 if err != nil { 102 if errors.Cause(err) == io.EOF { 103 inputs <- []string{leftover} 104 } else { 105 logrus.Error(err) 106 } 107 close(inputs) 108 if err := f.Close(); err != nil { 109 logrus.Error(err) 110 } 111 break 112 } 113 line := strings.Split(s+leftover, "\n") 114 if len(line) > 1 { 115 inputs <- line[1:] 116 } 117 leftover = line[0] 118 } 119 }() 120 121 for i := range inputs { 122 // the incoming array is FIFO; we want FIFO so 123 // reverse the slice read order 124 for j := len(i) - 1; j >= 0; j-- { 125 // lines that are "" are junk 126 if len(i[j]) < 1 { 127 continue 128 } 129 // read the content in reverse and add each nll until we have the same 130 // number of F type messages as the desired tail 131 nll, err := NewLogLine(i[j]) 132 if err != nil { 133 return nil, err 134 } 135 nlls = append(nlls, nll) 136 if !nll.Partial() { 137 nllCounter++ 138 } 139 } 140 // if we have enough loglines, we can hangup 141 if nllCounter >= tail { 142 break 143 } 144 } 145 146 // re-assemble the log lines and trim (if needed) to the 147 // tail length 148 for _, nll := range nlls { 149 if nll.Partial() { 150 partial += nll.Msg 151 } else { 152 nll.Msg += partial 153 // prepend because we need to reverse the order again to FIFO 154 tailLog = append([]*LogLine{nll}, tailLog...) 155 partial = "" 156 } 157 if len(tailLog) == tail { 158 break 159 } 160 } 161 return tailLog, nil 162 } 163 164 // String converts a logline to a string for output given whether a detail 165 // bool is specified. 166 func (l *LogLine) String(options *LogOptions) string { 167 var out string 168 if options.Multi { 169 if options.UseName { 170 out = l.CName + " " 171 } else { 172 cid := l.CID 173 if len(cid) > 12 { 174 cid = cid[:12] 175 } 176 out = fmt.Sprintf("%s ", cid) 177 } 178 } 179 if options.Timestamps { 180 out += fmt.Sprintf("%s ", l.Time.Format(LogTimeFormat)) 181 } 182 return out + l.Msg 183 } 184 185 // Since returns a bool as to whether a log line occurred after a given time 186 func (l *LogLine) Since(since time.Time) bool { 187 return l.Time.After(since) 188 } 189 190 // NewLogLine creates a logLine struct from a container log string 191 func NewLogLine(line string) (*LogLine, error) { 192 splitLine := strings.Split(line, " ") 193 if len(splitLine) < 4 { 194 return nil, errors.Errorf("'%s' is not a valid container log line", line) 195 } 196 logTime, err := time.Parse(LogTimeFormat, splitLine[0]) 197 if err != nil { 198 return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0]) 199 } 200 l := LogLine{ 201 Time: logTime, 202 Device: splitLine[1], 203 ParseLogType: splitLine[2], 204 Msg: strings.Join(splitLine[3:], " "), 205 } 206 return &l, nil 207 } 208 209 // Partial returns a bool if the log line is a partial log type 210 func (l *LogLine) Partial() bool { 211 return l.ParseLogType == PartialLogType 212 }