github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/libpod/events/logfile.go (about) 1 //go:build linux 2 // +build linux 3 4 package events 5 6 import ( 7 "bufio" 8 "context" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "path" 14 "time" 15 16 "github.com/hanks177/podman/v4/pkg/util" 17 "github.com/containers/storage/pkg/lockfile" 18 "github.com/nxadm/tail" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 21 "golang.org/x/sys/unix" 22 ) 23 24 // EventLogFile is the structure for event writing to a logfile. It contains the eventer 25 // options and the event itself. Methods for reading and writing are also defined from it. 26 type EventLogFile struct { 27 options EventerOptions 28 } 29 30 // Writes to the log file 31 func (e EventLogFile) Write(ee Event) error { 32 // We need to lock events file 33 lock, err := lockfile.GetLockfile(e.options.LogFilePath + ".lock") 34 if err != nil { 35 return err 36 } 37 lock.Lock() 38 defer lock.Unlock() 39 40 eventJSONString, err := ee.ToJSONString() 41 if err != nil { 42 return err 43 } 44 45 rotated, err := rotateLog(e.options.LogFilePath, eventJSONString, e.options.LogFileMaxSize) 46 if err != nil { 47 return fmt.Errorf("rotating log file: %w", err) 48 } 49 50 if rotated { 51 rEvent := NewEvent(Rotate) 52 rEvent.Type = System 53 rEvent.Name = e.options.LogFilePath 54 rotateJSONString, err := rEvent.ToJSONString() 55 if err != nil { 56 return err 57 } 58 if err := e.writeString(rotateJSONString); err != nil { 59 return err 60 } 61 } 62 63 return e.writeString(eventJSONString) 64 } 65 66 func (e EventLogFile) writeString(s string) error { 67 f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700) 68 if err != nil { 69 return err 70 } 71 if _, err := f.WriteString(s + "\n"); err != nil { 72 return err 73 } 74 return nil 75 } 76 77 func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) { 78 reopen := true 79 seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} 80 if options.FromStart || !options.Stream { 81 seek.Whence = 0 82 reopen = false 83 } 84 stream := options.Stream 85 return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true}) 86 } 87 88 // Reads from the log file 89 func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { 90 defer close(options.EventChannel) 91 filterMap, err := generateEventFilters(options.Filters, options.Since, options.Until) 92 if err != nil { 93 return errors.Wrapf(err, "failed to parse event filters") 94 } 95 t, err := e.getTail(options) 96 if err != nil { 97 return err 98 } 99 if len(options.Until) > 0 { 100 untilTime, err := util.ParseInputTime(options.Until, false) 101 if err != nil { 102 return err 103 } 104 go func() { 105 time.Sleep(time.Until(untilTime)) 106 if err := t.Stop(); err != nil { 107 logrus.Errorf("Stopping logger: %v", err) 108 } 109 }() 110 } 111 funcDone := make(chan bool) 112 copy := true 113 go func() { 114 select { 115 case <-funcDone: 116 // Do nothing 117 case <-ctx.Done(): 118 copy = false 119 t.Kill(errors.New("hangup by client")) 120 } 121 }() 122 for line := range t.Lines { 123 select { 124 case <-ctx.Done(): 125 // the consumer has cancelled 126 return nil 127 default: 128 // fallthrough 129 } 130 131 event, err := newEventFromJSONString(line.Text) 132 if err != nil { 133 return err 134 } 135 switch event.Type { 136 case Image, Volume, Pod, System, Container, Network: 137 // no-op 138 default: 139 return errors.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath) 140 } 141 if copy && applyFilters(event, filterMap) { 142 options.EventChannel <- event 143 } 144 } 145 funcDone <- true 146 return nil 147 } 148 149 // String returns a string representation of the logger 150 func (e EventLogFile) String() string { 151 return LogFile.String() 152 } 153 154 // Rotates the log file if the log file size and content exceeds limit 155 func rotateLog(logfile string, content string, limit uint64) (bool, error) { 156 if limit == 0 { 157 return false, nil 158 } 159 file, err := os.Stat(logfile) 160 if err != nil { 161 if errors.Is(err, os.ErrNotExist) { 162 // The logfile does not exist yet. 163 return false, nil 164 } 165 return false, err 166 } 167 var filesize = uint64(file.Size()) 168 var contentsize = uint64(len([]rune(content))) 169 if filesize+contentsize < limit { 170 return false, nil 171 } 172 173 if err := truncate(logfile); err != nil { 174 return false, err 175 } 176 return true, nil 177 } 178 179 // Truncates the log file and saves 50% of content to new log file 180 func truncate(filePath string) error { 181 orig, err := os.Open(filePath) 182 if err != nil { 183 return err 184 } 185 defer orig.Close() 186 187 origFinfo, err := orig.Stat() 188 if err != nil { 189 return err 190 } 191 192 size := origFinfo.Size() 193 threshold := size / 2 194 195 tmp, err := ioutil.TempFile(path.Dir(filePath), "") 196 if err != nil { 197 // Retry in /tmp in case creating a tmp file in the same 198 // directory has failed. 199 tmp, err = ioutil.TempFile("", "") 200 if err != nil { 201 return err 202 } 203 } 204 defer tmp.Close() 205 206 // Jump directly to the threshold, drop the first line and copy the remainder 207 if _, err := orig.Seek(threshold, 0); err != nil { 208 return err 209 } 210 reader := bufio.NewReader(orig) 211 if _, err := reader.ReadString('\n'); err != nil { 212 if !errors.Is(err, io.EOF) { 213 return err 214 } 215 } 216 if _, err := reader.WriteTo(tmp); err != nil { 217 return fmt.Errorf("writing truncated contents: %w", err) 218 } 219 220 if err := renameLog(tmp.Name(), filePath); err != nil { 221 return fmt.Errorf("writing back %s to %s: %w", tmp.Name(), filePath, err) 222 } 223 224 return nil 225 } 226 227 // Renames from, to 228 func renameLog(from, to string) error { 229 err := os.Rename(from, to) 230 if err == nil { 231 return nil 232 } 233 234 if !errors.Is(err, unix.EXDEV) { 235 return err 236 } 237 238 // Files are not on the same partition, so we need to copy them the 239 // hard way. 240 fFrom, err := os.Open(from) 241 if err != nil { 242 return err 243 } 244 defer fFrom.Close() 245 246 fTo, err := os.Create(to) 247 if err != nil { 248 return err 249 } 250 defer fTo.Close() 251 252 if _, err := io.Copy(fTo, fFrom); err != nil { 253 return fmt.Errorf("writing back from temporary file: %w", err) 254 } 255 256 if err := os.Remove(from); err != nil { 257 return fmt.Errorf("removing temporary file: %w", err) 258 } 259 260 return nil 261 }