vitess.io/vitess@v0.16.2/go/vt/logutil/purge.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package logutil 18 19 import ( 20 "fmt" 21 "os" 22 "path" 23 "path/filepath" 24 "strings" 25 "time" 26 27 "github.com/spf13/pflag" 28 29 _flag "vitess.io/vitess/go/internal/flag" 30 ) 31 32 var ( 33 keepLogsByCtime time.Duration 34 keepLogsByMtime time.Duration 35 purgeLogsInterval = 1 * time.Hour 36 ) 37 38 // RegisterFlags installs logutil flags on the given FlagSet. 39 // 40 // `go/cmd/*` entrypoints should either use servenv.ParseFlags(WithArgs)? which 41 // calls this function, or call this function directly before parsing 42 // command-line arguments. 43 func RegisterFlags(fs *pflag.FlagSet) { 44 fs.DurationVar(&keepLogsByCtime, "keep_logs", keepLogsByCtime, "keep logs for this long (using ctime) (zero to keep forever)") 45 fs.DurationVar(&keepLogsByMtime, "keep_logs_by_mtime", keepLogsByMtime, "keep logs for this long (using mtime) (zero to keep forever)") 46 fs.DurationVar(&purgeLogsInterval, "purge_logs_interval", purgeLogsInterval, "how often try to remove old logs") 47 } 48 49 // parse parses a file name (as used by glog) and returns its process 50 // name and timestamp. 51 func parseCreatedTimestamp(filename string) (timestamp time.Time, err error) { 52 parts := strings.Split(filepath.Base(filename), ".") 53 if len(parts) < 6 { 54 return time.Time{}, fmt.Errorf("malformed logfile name: %v", filename) 55 } 56 return time.ParseInLocation("20060102-150405", parts[len(parts)-2], time.Now().Location()) 57 58 } 59 60 func getModifiedTimestamp(filename string) (timestamp time.Time, err error) { 61 fileInfo, err := os.Stat(filename) 62 if err != nil { 63 return time.Time{}, err 64 } 65 return fileInfo.ModTime(), nil 66 } 67 68 var levels = []string{"INFO", "ERROR", "WARNING", "FATAL"} 69 70 // purgeLogsOnce removes logfiles for program for dir, if their age 71 // relative to now is greater than [cm]timeDelta 72 func purgeLogsOnce(now time.Time, dir, program string, ctimeDelta time.Duration, mtimeDelta time.Duration) { 73 current := make(map[string]bool) 74 for _, level := range levels { 75 c, err := filepath.EvalSymlinks(path.Join(dir, fmt.Sprintf("%s.%s", program, level))) 76 if err != nil { 77 continue 78 } 79 current[c] = true 80 } 81 82 files, err := filepath.Glob(path.Join(dir, fmt.Sprintf("%s.*", program))) 83 if err != nil { 84 return 85 } 86 for _, file := range files { 87 statInfo, err := os.Lstat(file) 88 if err != nil { 89 // Failed to stat file 90 continue 91 } 92 if current[file] || !statInfo.Mode().IsRegular() { 93 // Do not purge current file or any non-regular files (symlinks etc) 94 continue 95 } 96 purgeFile := false 97 if ctimeDelta != 0 { 98 createdTs, err := parseCreatedTimestamp(file) 99 if err != nil { 100 continue 101 } 102 purgeFile = purgeFile || now.Sub(createdTs) > ctimeDelta 103 } 104 if mtimeDelta != 0 { 105 modifiedTs, err := getModifiedTimestamp(file) 106 if err != nil { 107 continue 108 } 109 purgeFile = purgeFile || now.Sub(modifiedTs) > mtimeDelta 110 } 111 if purgeFile { 112 os.Remove(file) 113 } 114 } 115 } 116 117 // PurgeLogs removes any log files that were started more than 118 // keepLogs ago and that aren't the current log. 119 func PurgeLogs() { 120 f := _flag.Lookup("log_dir") 121 if f == nil { 122 panic("the logging module doesn't specify a log_dir flag") 123 } 124 if keepLogsByCtime == 0 && keepLogsByMtime == 0 { 125 return 126 } 127 logDir := f.Value.String() 128 program := filepath.Base(os.Args[0]) 129 ticker := time.NewTicker(purgeLogsInterval) 130 131 go func() { 132 for range ticker.C { 133 purgeLogsOnce(time.Now(), logDir, program, keepLogsByCtime, keepLogsByMtime) 134 } 135 }() 136 }