github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/debug/debug.go (about) 1 // +build debug 2 3 package debug 4 5 import ( 6 "fmt" 7 "log" 8 "os" 9 "path" 10 "path/filepath" 11 "runtime" 12 "strings" 13 14 "github.com/restic/restic/internal/fs" 15 16 "github.com/restic/restic/internal/errors" 17 ) 18 19 var opts struct { 20 logger *log.Logger 21 funcs map[string]bool 22 files map[string]bool 23 } 24 25 // make sure that all the initialization happens before the init() functions 26 // are called, cf https://golang.org/ref/spec#Package_initialization 27 var _ = initDebug() 28 29 func initDebug() bool { 30 initDebugLogger() 31 initDebugTags() 32 33 fmt.Fprintf(os.Stderr, "debug enabled\n") 34 35 return true 36 } 37 38 func initDebugLogger() { 39 debugfile := os.Getenv("DEBUG_LOG") 40 if debugfile == "" { 41 return 42 } 43 44 fmt.Fprintf(os.Stderr, "debug log file %v\n", debugfile) 45 46 f, err := fs.OpenFile(debugfile, os.O_WRONLY|os.O_APPEND, 0600) 47 48 if err == nil { 49 _, err = f.Seek(2, 0) 50 if err != nil { 51 fmt.Fprintf(os.Stderr, "unable to seek to the end of %v: %v\n", debugfile, err) 52 os.Exit(3) 53 } 54 } 55 56 if err != nil && os.IsNotExist(errors.Cause(err)) { 57 f, err = fs.OpenFile(debugfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 58 } 59 60 if err != nil { 61 fmt.Fprintf(os.Stderr, "unable to open debug log file: %v\n", err) 62 os.Exit(2) 63 } 64 65 opts.logger = log.New(f, "", log.LstdFlags) 66 } 67 68 func parseFilter(envname string, pad func(string) string) map[string]bool { 69 filter := make(map[string]bool) 70 71 env := os.Getenv(envname) 72 if env == "" { 73 return filter 74 } 75 76 for _, fn := range strings.Split(env, ",") { 77 t := pad(strings.TrimSpace(fn)) 78 val := true 79 if t[0] == '-' { 80 val = false 81 t = t[1:] 82 } else if t[0] == '+' { 83 val = true 84 t = t[1:] 85 } 86 87 // test pattern 88 _, err := path.Match(t, "") 89 if err != nil { 90 fmt.Fprintf(os.Stderr, "error: invalid pattern %q: %v\n", t, err) 91 os.Exit(5) 92 } 93 94 filter[t] = val 95 } 96 97 return filter 98 } 99 100 func padFunc(s string) string { 101 if s == "all" { 102 return s 103 } 104 105 return s 106 } 107 108 func padFile(s string) string { 109 if s == "all" { 110 return s 111 } 112 113 if !strings.Contains(s, "/") { 114 s = "*/" + s 115 } 116 117 if !strings.Contains(s, ":") { 118 s = s + ":*" 119 } 120 121 return s 122 } 123 124 func initDebugTags() { 125 opts.funcs = parseFilter("DEBUG_FUNCS", padFunc) 126 opts.files = parseFilter("DEBUG_FILES", padFile) 127 } 128 129 // taken from https://github.com/VividCortex/trace 130 func goroutineNum() int { 131 b := make([]byte, 20) 132 runtime.Stack(b, false) 133 var num int 134 135 fmt.Sscanf(string(b), "goroutine %d ", &num) 136 return num 137 } 138 139 // taken from https://github.com/VividCortex/trace 140 func getPosition() (fn, dir, file string, line int) { 141 pc, file, line, ok := runtime.Caller(2) 142 if !ok { 143 return "", "", "", 0 144 } 145 146 dirname, filename := filepath.Base(filepath.Dir(file)), filepath.Base(file) 147 148 Func := runtime.FuncForPC(pc) 149 150 return path.Base(Func.Name()), dirname, filename, line 151 } 152 153 func checkFilter(filter map[string]bool, key string) bool { 154 // check if key is enabled directly 155 if v, ok := filter[key]; ok { 156 return v 157 } 158 159 // check for globbing 160 for k, v := range filter { 161 if m, _ := path.Match(k, key); m { 162 return v 163 } 164 } 165 166 // check if tag "all" is enabled 167 if v, ok := filter["all"]; ok && v { 168 return true 169 } 170 171 return false 172 } 173 174 // Log prints a message to the debug log (if debug is enabled). 175 func Log(f string, args ...interface{}) { 176 fn, dir, file, line := getPosition() 177 goroutine := goroutineNum() 178 179 if len(f) == 0 || f[len(f)-1] != '\n' { 180 f += "\n" 181 } 182 183 pos := fmt.Sprintf("%s/%s:%d", dir, file, line) 184 185 formatString := fmt.Sprintf("%s\t%s\t%d\t%s", pos, fn, goroutine, f) 186 187 dbgprint := func() { 188 fmt.Fprintf(os.Stderr, formatString, args...) 189 } 190 191 if opts.logger != nil { 192 opts.logger.Printf(formatString, args...) 193 } 194 195 if checkFilter(opts.files, fmt.Sprintf("%s/%s:%d", dir, file, line)) { 196 dbgprint() 197 return 198 } 199 200 if checkFilter(opts.funcs, fn) { 201 dbgprint() 202 } 203 }