github.com/Cloud-Foundations/Dominator@v0.3.4/lib/logbuf/impl.go (about) 1 package logbuf 2 3 import ( 4 "bufio" 5 "container/ring" 6 "errors" 7 "flag" 8 "fmt" 9 "html" 10 "io" 11 "os" 12 "path" 13 "sort" 14 "strings" 15 "time" 16 17 "github.com/Cloud-Foundations/Dominator/lib/bufwriter" 18 "github.com/Cloud-Foundations/Dominator/lib/flagutil" 19 "github.com/Cloud-Foundations/Dominator/lib/format" 20 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 21 "github.com/Cloud-Foundations/Dominator/lib/wsyscall" 22 ) 23 24 const ( 25 reopenMessage = "Closing and will open new logfile" 26 timeLayout = "2006-01-02:15:04:05.999" 27 ) 28 29 func newLogBuffer(options Options) *LogBuffer { 30 if options.MaxBufferLines < 100 { 31 options.MaxBufferLines = 100 32 } 33 if options.MaxFileSize < 16384 { 34 options.MaxFileSize = 16384 35 } 36 if options.Quota < 65536 { 37 options.Quota = 65536 38 } 39 if options.AlsoLogToStderr { 40 options.RedirectStderr = false // Prevent duplicates sent to stderr. 41 } 42 logBuffer := &LogBuffer{ 43 options: options, 44 buffer: ring.New(int(options.MaxBufferLines)), 45 } 46 if err := logBuffer.setupFileLogging(); err != nil { 47 fmt.Fprintln(logBuffer, err) 48 } 49 logBuffer.addHttpHandlers() 50 return logBuffer 51 } 52 53 func (lb *LogBuffer) setupFileLogging() error { 54 if lb.options.Directory == "" { 55 return nil 56 } 57 if err := lb.createLogDirectory(); err != nil { 58 return err 59 } 60 writeNotifier := make(chan struct{}, 1) 61 lb.writeNotifier = writeNotifier 62 go lb.idleMarker(writeNotifier) 63 return nil 64 } 65 66 func (lb *LogBuffer) checkNeverLogged() bool { 67 lb.rwMutex.Lock() 68 defer lb.rwMutex.Unlock() 69 return lb.noLogsEver 70 } 71 72 func (lb *LogBuffer) createLogDirectory() error { 73 if fi, err := os.Stat(lb.options.Directory); err != nil { 74 if err := os.Mkdir(lb.options.Directory, dirPerms); err != nil { 75 return fmt.Errorf("error creating: %s: %s", 76 lb.options.Directory, err) 77 } 78 lb.noLogsEver = true 79 } else if !fi.IsDir() { 80 return errors.New(lb.options.Directory + ": is not a directory") 81 } else { 82 names, err := fsutil.ReadDirnames(lb.options.Directory, false) 83 if err != nil { 84 return err 85 } else if len(names) < 1 { 86 lb.noLogsEver = true 87 } 88 } 89 lb.scanPreviousForPanic() 90 return lb.enforceQuota() 91 } 92 93 func (lb *LogBuffer) scanPreviousForPanic() { 94 target, err := os.Readlink(path.Join(lb.options.Directory, "latest")) 95 if err != nil { 96 return 97 } 98 targetPath := path.Join(lb.options.Directory, target) 99 file, err := os.Open(targetPath) 100 if err != nil { 101 return 102 } 103 go func() { 104 defer file.Close() 105 scanner := bufio.NewScanner(file) 106 var previousLineHadPanicString bool 107 for scanner.Scan() { 108 line := scanner.Text() 109 var looksLikeAPanic bool 110 if strings.HasPrefix(line, "panic: ") { 111 looksLikeAPanic = true 112 } else if strings.HasPrefix(line, "goroutine ") { 113 if previousLineHadPanicString { 114 looksLikeAPanic = true 115 } 116 } else if strings.Contains(line, "panic") { 117 previousLineHadPanicString = true 118 } else { 119 previousLineHadPanicString = false 120 } 121 if looksLikeAPanic { 122 lb.rwMutex.Lock() 123 lb.panicLogfile = &target 124 lb.rwMutex.Unlock() 125 if fi, err := os.Stat(targetPath); err != nil { 126 return 127 } else { 128 os.Chmod(targetPath, fi.Mode()|os.ModeSticky) 129 } 130 return 131 } 132 } 133 }() 134 } 135 136 func (lb *LogBuffer) dump(writer io.Writer, prefix, postfix string, 137 recentFirst bool, escapeHtml bool) error { 138 entries := lb.getEntries() 139 if recentFirst { 140 reverseEntries(entries) 141 } 142 for _, entry := range entries { 143 writer.Write([]byte(prefix)) 144 if escapeHtml { 145 writer.Write([]byte(html.EscapeString(string(entry)))) 146 } else { 147 writer.Write(entry) 148 } 149 writer.Write([]byte(postfix)) 150 } 151 return nil 152 } 153 154 func (lb *LogBuffer) flush() error { 155 lb.rwMutex.Lock() 156 defer lb.rwMutex.Unlock() 157 if lb.writer != nil { 158 return lb.writer.Flush() 159 } 160 return nil 161 } 162 163 func (lb *LogBuffer) write(p []byte) (n int, err error) { 164 if lb.options.AlsoLogToStderr { 165 os.Stderr.Write(p) 166 } 167 val := make([]byte, len(p)) 168 copy(val, p) 169 lb.rwMutex.Lock() 170 sendNotify := lb.writeToLogFile(p) 171 lb.buffer.Value = val 172 lb.buffer = lb.buffer.Next() 173 lb.rwMutex.Unlock() 174 if sendNotify { 175 lb.writeNotifier <- struct{}{} 176 } 177 return len(p), nil 178 } 179 180 // This should be called with the lock held. 181 func (lb *LogBuffer) writeToLogFile(p []byte) bool { 182 if lb.writer == nil { 183 return false 184 } 185 lb.writer.Write(p) 186 lb.fileSize += flagutil.Size(len(p)) 187 if lb.fileSize > lb.options.MaxFileSize { 188 lb.closeAndOpenNewFile() 189 } 190 lb.usage += flagutil.Size(len(p)) 191 if lb.usage > lb.options.Quota { 192 lb.enforceQuota() 193 } 194 return true 195 } 196 197 // This should be called with the lock held. 198 func (lb *LogBuffer) closeAndOpenNewFile() error { 199 now := time.Now() 200 year, month, day := now.Date() 201 hour, minute, second := now.Clock() 202 nWritten, _ := fmt.Fprintf(lb.writer, "%d/%02d/%02d %02d:%02d:%02d %s\n", 203 year, month, day, hour, minute, second, reopenMessage) 204 lb.usage += flagutil.Size(nWritten) 205 lb.writer.Flush() 206 lb.writer = nil 207 lb.file.Close() 208 if err := lb.openNewFile(); err != nil { 209 return err 210 } 211 return nil 212 } 213 214 // This should be called with the lock held. 215 func (lb *LogBuffer) openNewFile() error { 216 lb.fileSize = 0 217 filename := time.Now().Format(timeLayout) 218 file, err := os.OpenFile(path.Join(lb.options.Directory, filename), 219 os.O_CREATE|os.O_WRONLY, filePerms) 220 if err != nil { 221 return err 222 } 223 if lb.firstFile == "" { 224 lb.firstFile = filename 225 } 226 if lb.options.RedirectStderr { 227 wsyscall.Dup2(int(file.Fd()), int(os.Stdout.Fd())) 228 wsyscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())) 229 } 230 lb.file = file 231 lb.writer = bufwriter.NewWriter(file, time.Second) 232 symlink := path.Join(lb.options.Directory, "latest") 233 tmpSymlink := symlink + "~" 234 os.Remove(tmpSymlink) 235 os.Symlink(filename, tmpSymlink) 236 return os.Rename(tmpSymlink, symlink) 237 } 238 239 // This should be called with the lock held. 240 func (lb *LogBuffer) enforceQuota() error { 241 file, err := os.Open(lb.options.Directory) 242 if err != nil { 243 return err 244 } 245 names, err := file.Readdirnames(-1) 246 file.Close() 247 if err != nil { 248 return err 249 } 250 sort.Strings(names) 251 var numBytesDeleted, numFilesDeleted uint64 252 var usage flagutil.Size 253 deletedLatestFile := false 254 deleteRemainingFiles := false 255 latestFile := true 256 for index := len(names) - 1; index >= 0; index-- { 257 filename := path.Join(lb.options.Directory, names[index]) 258 fi, err := os.Lstat(filename) 259 if err == os.ErrNotExist { 260 continue 261 } 262 if err != nil { 263 return err 264 } 265 if fi.Mode().IsRegular() { 266 size := flagutil.Size(fi.Size()) 267 if size < lb.options.Quota>>10 { 268 size = lb.options.Quota >> 10 // Limit number of files to 1024. 269 } 270 if size+usage > lb.options.Quota || deleteRemainingFiles { 271 os.Remove(filename) 272 numBytesDeleted += uint64(fi.Size()) 273 numFilesDeleted++ 274 deleteRemainingFiles = true 275 if latestFile { 276 deletedLatestFile = true 277 } 278 } else { 279 usage += size 280 } 281 latestFile = false 282 } 283 } 284 lb.usage = usage 285 if deletedLatestFile && lb.file != nil { 286 lb.writer.Flush() 287 lb.writer = nil 288 lb.file.Close() 289 lb.file = nil 290 } 291 if lb.file == nil { 292 if err := lb.openNewFile(); err != nil { 293 return err 294 } 295 } 296 if numBytesDeleted > 0 { 297 now := time.Now() 298 year, month, day := now.Date() 299 hour, minute, second := now.Clock() 300 nWritten, _ := fmt.Fprintf(lb.writer, 301 "%d/%02d/%02d %02d:%02d:%02d Deleted %s in %d files\n", 302 year, month, day, hour, minute, second, 303 format.FormatBytes(numBytesDeleted), numFilesDeleted) 304 lb.fileSize += flagutil.Size(nWritten) 305 lb.usage += flagutil.Size(nWritten) 306 } 307 return nil 308 } 309 310 func (lb *LogBuffer) idleMarker(writeNotifier <-chan struct{}) { 311 idleMarkDuration := lb.options.IdleMarkTimeout 312 if idleMarkDuration < 1 { 313 for { 314 <-writeNotifier 315 } 316 } 317 idleMarkTimer := time.NewTimer(idleMarkDuration) 318 for { 319 select { 320 case <-writeNotifier: 321 idleMarkTimer.Reset(idleMarkDuration) 322 case <-idleMarkTimer.C: 323 lb.writeMark() 324 idleMarkTimer.Reset(idleMarkDuration) 325 } 326 } 327 } 328 329 func (lb *LogBuffer) getEntries() [][]byte { 330 lb.rwMutex.RLock() 331 defer lb.rwMutex.RUnlock() 332 entries := make([][]byte, 0, lb.buffer.Len()) 333 lb.buffer.Do(func(p interface{}) { 334 if p != nil { 335 entries = append(entries, p.([]byte)) 336 } 337 }) 338 return entries 339 } 340 341 func (lb *LogBuffer) dumpSince(writer io.Writer, name string, 342 earliestTime time.Time, prefix, postfix string, recentFirst bool) ( 343 bool, error) { 344 file, err := os.Open(path.Join(lb.options.Directory, 345 path.Base(path.Clean(name)))) 346 if err != nil { 347 return false, err 348 } 349 defer file.Close() 350 scanner := bufio.NewScanner(file) 351 lines := make([]string, 0) 352 timeFormat := "2006/01/02 15:04:05" 353 minLength := len(timeFormat) + 2 354 foundReopenMessage := false 355 for scanner.Scan() { 356 line := scanner.Text() 357 if strings.Contains(line, reopenMessage) { 358 foundReopenMessage = true 359 continue 360 } 361 if len(line) >= minLength { 362 timeString := line[:minLength-2] 363 timeStamp, err := time.ParseInLocation(timeFormat, timeString, 364 time.Local) 365 if err == nil && timeStamp.Before(earliestTime) { 366 continue 367 } 368 } 369 if recentFirst { 370 lines = append(lines, line) 371 } else { 372 writer.Write([]byte(prefix)) 373 writer.Write([]byte(line)) 374 writer.Write([]byte(postfix)) 375 } 376 } 377 if err := scanner.Err(); err != nil { 378 return false, err 379 } 380 if recentFirst { 381 reverseStrings(lines) 382 for _, line := range lines { 383 writer.Write([]byte(prefix)) 384 writer.Write([]byte(line)) 385 writer.Write([]byte(postfix)) 386 } 387 } 388 return foundReopenMessage, nil 389 } 390 391 func (lb *LogBuffer) writeMark() { 392 now := time.Now() 393 year, month, day := now.Date() 394 hour, minute, second := now.Clock() 395 str := fmt.Sprintf("%d/%02d/%02d %02d:%02d:%02d MARK\n", 396 year, month, day, hour, minute, second) 397 lb.rwMutex.Lock() 398 defer lb.rwMutex.Unlock() 399 lb.writeToLogFile([]byte(str)) 400 } 401 402 func reverseEntries(entries [][]byte) { 403 length := len(entries) 404 for index := 0; index < length/2; index++ { 405 entries[index], entries[length-1-index] = 406 entries[length-1-index], entries[index] 407 } 408 } 409 410 func reverseStrings(strings []string) { 411 length := len(strings) 412 for index := 0; index < length/2; index++ { 413 strings[index], strings[length-1-index] = 414 strings[length-1-index], strings[index] 415 } 416 } 417 418 func init() { 419 UseFlagSet(flag.CommandLine) 420 }