github.com/AbhinandanKurakure/podman/v3@v3.4.10/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/v3/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 Until time.Time 38 Tail int64 39 Timestamps bool 40 Multi bool 41 WaitGroup *sync.WaitGroup 42 UseName bool 43 } 44 45 // LogLine describes the information for each line of a log 46 type LogLine struct { 47 Device string 48 ParseLogType string 49 Time time.Time 50 Msg string 51 CID string 52 CName string 53 } 54 55 // GetLogFile returns an hp tail for a container given options 56 func GetLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error) { 57 var ( 58 whence int 59 err error 60 logTail []*LogLine 61 ) 62 // whence 0=origin, 2=end 63 if options.Tail >= 0 { 64 whence = 2 65 } 66 if options.Tail > 0 { 67 logTail, err = getTailLog(path, int(options.Tail)) 68 if err != nil { 69 return nil, nil, err 70 } 71 } 72 seek := tail.SeekInfo{ 73 Offset: 0, 74 Whence: whence, 75 } 76 77 t, err := tail.TailFile(path, tail.Config{MustExist: true, Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger, ReOpen: options.Follow}) 78 return t, logTail, err 79 } 80 81 func getTailLog(path string, tail int) ([]*LogLine, error) { 82 var ( 83 nlls []*LogLine 84 nllCounter int 85 leftover string 86 partial string 87 tailLog []*LogLine 88 ) 89 f, err := os.Open(path) 90 if err != nil { 91 return nil, err 92 } 93 rr, err := reversereader.NewReverseReader(f) 94 if err != nil { 95 return nil, err 96 } 97 98 inputs := make(chan []string) 99 go func() { 100 for { 101 s, err := rr.Read() 102 if err != nil { 103 if errors.Cause(err) == io.EOF { 104 inputs <- []string{leftover} 105 } else { 106 logrus.Error(err) 107 } 108 close(inputs) 109 if err := f.Close(); err != nil { 110 logrus.Error(err) 111 } 112 break 113 } 114 line := strings.Split(s+leftover, "\n") 115 if len(line) > 1 { 116 inputs <- line[1:] 117 } 118 leftover = line[0] 119 } 120 }() 121 122 for i := range inputs { 123 // the incoming array is FIFO; we want FIFO so 124 // reverse the slice read order 125 for j := len(i) - 1; j >= 0; j-- { 126 // lines that are "" are junk 127 if len(i[j]) < 1 { 128 continue 129 } 130 // read the content in reverse and add each nll until we have the same 131 // number of F type messages as the desired tail 132 nll, err := NewLogLine(i[j]) 133 if err != nil { 134 return nil, err 135 } 136 nlls = append(nlls, nll) 137 if !nll.Partial() { 138 nllCounter++ 139 } 140 } 141 // if we have enough log lines, we can hangup 142 if nllCounter >= tail { 143 break 144 } 145 } 146 147 // re-assemble the log lines and trim (if needed) to the 148 // tail length 149 for _, nll := range nlls { 150 if nll.Partial() { 151 partial += nll.Msg 152 } else { 153 nll.Msg += partial 154 // prepend because we need to reverse the order again to FIFO 155 tailLog = append([]*LogLine{nll}, tailLog...) 156 partial = "" 157 } 158 if len(tailLog) == tail { 159 break 160 } 161 } 162 return tailLog, nil 163 } 164 165 // String converts a log line to a string for output given whether a detail 166 // bool is specified. 167 func (l *LogLine) String(options *LogOptions) string { 168 var out string 169 if options.Multi { 170 if options.UseName { 171 out = l.CName + " " 172 } else { 173 cid := l.CID 174 if len(cid) > 12 { 175 cid = cid[:12] 176 } 177 out = fmt.Sprintf("%s ", cid) 178 } 179 } 180 if options.Timestamps { 181 out += fmt.Sprintf("%s ", l.Time.Format(LogTimeFormat)) 182 } 183 return out + l.Msg 184 } 185 186 // Since returns a bool as to whether a log line occurred after a given time 187 func (l *LogLine) Since(since time.Time) bool { 188 return l.Time.After(since) || since.IsZero() 189 } 190 191 // Until returns a bool as to whether a log line occurred before a given time 192 func (l *LogLine) Until(until time.Time) bool { 193 return l.Time.Before(until) || until.IsZero() 194 } 195 196 // NewLogLine creates a logLine struct from a container log string 197 func NewLogLine(line string) (*LogLine, error) { 198 splitLine := strings.Split(line, " ") 199 if len(splitLine) < 4 { 200 return nil, errors.Errorf("'%s' is not a valid container log line", line) 201 } 202 logTime, err := time.Parse(LogTimeFormat, splitLine[0]) 203 if err != nil { 204 return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0]) 205 } 206 l := LogLine{ 207 Time: logTime, 208 Device: splitLine[1], 209 ParseLogType: splitLine[2], 210 Msg: strings.Join(splitLine[3:], " "), 211 } 212 return &l, nil 213 } 214 215 // NewJournaldLogLine creates a LogLine from the specified line from journald. 216 // Note that if withID is set, the first item of the message is considerred to 217 // be the container ID and set as such. 218 func NewJournaldLogLine(line string, withID bool) (*LogLine, error) { 219 splitLine := strings.Split(line, " ") 220 if len(splitLine) < 4 { 221 return nil, errors.Errorf("'%s' is not a valid container log line", line) 222 } 223 logTime, err := time.Parse(LogTimeFormat, splitLine[0]) 224 if err != nil { 225 return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0]) 226 } 227 var msg, id string 228 if withID { 229 id = splitLine[3] 230 msg = strings.Join(splitLine[4:], " ") 231 } else { 232 msg = strings.Join(splitLine[3:], " ") 233 // NO ID 234 } 235 l := LogLine{ 236 Time: logTime, 237 Device: splitLine[1], 238 ParseLogType: splitLine[2], 239 Msg: msg, 240 CID: id, 241 } 242 return &l, nil 243 } 244 245 // Partial returns a bool if the log line is a partial log type 246 func (l *LogLine) Partial() bool { 247 return l.ParseLogType == PartialLogType 248 } 249 250 func (l *LogLine) Write(stdout io.Writer, stderr io.Writer, logOpts *LogOptions) { 251 switch l.Device { 252 case "stdout": 253 if stdout != nil { 254 fmt.Fprintln(stdout, l.String(logOpts)) 255 } 256 case "stderr": 257 if stderr != nil { 258 fmt.Fprintln(stderr, l.String(logOpts)) 259 } 260 default: 261 // Warn the user if the device type does not match. Most likely the file is corrupted. 262 logrus.Warnf("unknown Device type '%s' in log file from Container %s", l.Device, l.CID) 263 } 264 }