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  }