├── History.md ├── Makefile ├── Readme.md ├── debug.go ├── debug_test.go └── example ├── multiple.go └── single.go /History.md: -------------------------------------------------------------------------------- 1 | 2 | v2.0.0 / 2014-10-22 3 | ================== 4 | 5 | * remove live toggling feature. Closes #10 6 | 7 | 1.1.1 / 2014-07-07 8 | ================== 9 | 10 | * fix: dispose socket. Closes #1 11 | 12 | 1.1.0 / 2014-06-29 13 | ================== 14 | 15 | * add unix domain socket live debugging support 16 | * add support for enabling/disabling at runtime 17 | 18 | 0.1.0 / 2014-05-24 19 | ================== 20 | 21 | * add global and debug relative deltas 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @go test 4 | 5 | bench: 6 | @go test -bench=. 7 | 8 | .PHONY: bench test -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # go-debug 3 | 4 | Conditional debug logging for Go libraries. 5 | 6 | View the [docs](http://godoc.org/github.com/tj/go-debug). 7 | 8 | ## Installation 9 | 10 | ``` 11 | $ go get github.com/tj/go-debug 12 | ``` 13 | 14 | ## Example 15 | 16 | ```go 17 | package main 18 | 19 | import . "github.com/tj/go-debug" 20 | import "time" 21 | 22 | var debug = Debug("single") 23 | 24 | func main() { 25 | for { 26 | debug("sending mail") 27 | debug("send email to %s", "tobi@segment.io") 28 | debug("send email to %s", "loki@segment.io") 29 | debug("send email to %s", "jane@segment.io") 30 | time.Sleep(500 * time.Millisecond) 31 | } 32 | } 33 | ``` 34 | 35 | If you run the program with the `DEBUG=*` environment variable you will see: 36 | 37 | ``` 38 | 15:58:15.115 34us 33us single - sending mail 39 | 15:58:15.116 3us 3us single - send email to tobi@segment.io 40 | 15:58:15.116 1us 1us single - send email to loki@segment.io 41 | 15:58:15.116 1us 1us single - send email to jane@segment.io 42 | 15:58:15.620 504ms 504ms single - sending mail 43 | 15:58:15.620 6us 6us single - send email to tobi@segment.io 44 | 15:58:15.620 4us 4us single - send email to loki@segment.io 45 | 15:58:15.620 4us 4us single - send email to jane@segment.io 46 | 15:58:16.123 503ms 503ms single - sending mail 47 | 15:58:16.123 7us 7us single - send email to tobi@segment.io 48 | 15:58:16.123 4us 4us single - send email to loki@segment.io 49 | 15:58:16.123 4us 4us single - send email to jane@segment.io 50 | 15:58:16.625 501ms 501ms single - sending mail 51 | 15:58:16.625 4us 4us single - send email to tobi@segment.io 52 | 15:58:16.625 4us 4us single - send email to loki@segment.io 53 | 15:58:16.625 5us 5us single - send email to jane@segment.io 54 | ``` 55 | 56 | A timestamp and two deltas are displayed. The timestamp consists of hour, minute, second and microseconds. The left-most delta is relative to the previous debug call of any name, followed by a delta specific to that debug function. These may be useful to identify timing issues and potential bottlenecks. 57 | 58 | ## The DEBUG environment variable 59 | 60 | Executables often support `--verbose` flags for conditional logging, however 61 | libraries typically either require altering your code to enable logging, 62 | or simply omit logging all together. go-debug allows conditional logging 63 | to be enabled via the __DEBUG__ environment variable, where one or more 64 | patterns may be specified. 65 | 66 | For example suppose your application has several models and you want 67 | to output logs for users only, you might use `DEBUG=models:user`. In contrast 68 | if you wanted to see what all database activity was you might use `DEBUG=models:*`, 69 | or if you're love being swamped with logs: `DEBUG=*`. You may also specify a list of names delimited by a comma, for example `DEBUG=mongo,redis:*`. 70 | 71 | The name given _should_ be the package name, however you can use whatever you like. 72 | 73 | # License 74 | 75 | MIT -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "math/rand" 7 | "os" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | var ( 16 | writer io.Writer = os.Stderr 17 | reg *regexp.Regexp 18 | m sync.Mutex 19 | enabled = false 20 | ) 21 | 22 | // Debugger function. 23 | type DebugFunction func(string, ...interface{}) 24 | 25 | // Terminal colors used at random. 26 | var colors []string = []string{ 27 | "31", 28 | "32", 29 | "33", 30 | "34", 31 | "35", 32 | "36", 33 | } 34 | 35 | // Initialize with DEBUG environment variable. 36 | func init() { 37 | env := os.Getenv("DEBUG") 38 | 39 | if "" != env { 40 | Enable(env) 41 | } 42 | } 43 | 44 | // SetWriter replaces the default of os.Stderr with `w`. 45 | func SetWriter(w io.Writer) { 46 | m.Lock() 47 | defer m.Unlock() 48 | writer = w 49 | } 50 | 51 | // Disable all pattern matching. This function is thread-safe. 52 | func Disable() { 53 | m.Lock() 54 | defer m.Unlock() 55 | enabled = false 56 | } 57 | 58 | // Enable the given debug `pattern`. Patterns take a glob-like form, 59 | // for example if you wanted to enable everything, just use "*", or 60 | // if you had a library named mongodb you could use "mongodb:connection", 61 | // or "mongodb:*". Multiple matches can be made with a comma, for 62 | // example "mongo*,redis*". 63 | // 64 | // This function is thread-safe. 65 | func Enable(pattern string) { 66 | m.Lock() 67 | defer m.Unlock() 68 | pattern = regexp.QuoteMeta(pattern) 69 | pattern = strings.Replace(pattern, "\\*", ".*?", -1) 70 | pattern = strings.Replace(pattern, ",", "|", -1) 71 | pattern = "^(" + pattern + ")$" 72 | reg = regexp.MustCompile(pattern) 73 | enabled = true 74 | } 75 | 76 | // Debug creates a debug function for `name` which you call 77 | // with printf-style arguments in your application or library. 78 | func Debug(name string) DebugFunction { 79 | prevGlobal := time.Now() 80 | color := colors[rand.Intn(len(colors))] 81 | prev := time.Now() 82 | 83 | return func(format string, args ...interface{}) { 84 | if !enabled { 85 | return 86 | } 87 | 88 | if !reg.MatchString(name) { 89 | return 90 | } 91 | 92 | d := deltas(prevGlobal, prev, color) 93 | fmt.Fprintf(writer, d+" \033["+color+"m"+name+"\033[0m - "+format+"\n", args...) 94 | prevGlobal = time.Now() 95 | prev = time.Now() 96 | } 97 | } 98 | 99 | // Return formatting for deltas. 100 | func deltas(prevGlobal, prev time.Time, color string) string { 101 | now := time.Now() 102 | global := now.Sub(prevGlobal).Nanoseconds() 103 | delta := now.Sub(prev).Nanoseconds() 104 | ts := now.UTC().Format("15:04:05.000") 105 | deltas := fmt.Sprintf("%s %-6s \033["+color+"m%-6s", ts, humanizeNano(global), humanizeNano(delta)) 106 | return deltas 107 | } 108 | 109 | // Humanize nanoseconds to a string. 110 | func humanizeNano(n int64) string { 111 | var suffix string 112 | 113 | switch { 114 | case n > 1e9: 115 | n /= 1e9 116 | suffix = "s" 117 | case n > 1e6: 118 | n /= 1e6 119 | suffix = "ms" 120 | case n > 1e3: 121 | n /= 1e3 122 | suffix = "us" 123 | default: 124 | suffix = "ns" 125 | } 126 | 127 | return strconv.Itoa(int(n)) + suffix 128 | } 129 | -------------------------------------------------------------------------------- /debug_test.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import "testing" 4 | import "strings" 5 | import "bytes" 6 | import "time" 7 | 8 | func assertContains(t *testing.T, str, substr string) { 9 | if !strings.Contains(str, substr) { 10 | t.Fatalf("expected %q to contain %q", str, substr) 11 | } 12 | } 13 | 14 | func assertNotContains(t *testing.T, str, substr string) { 15 | if strings.Contains(str, substr) { 16 | t.Fatalf("expected %q to not contain %q", str, substr) 17 | } 18 | } 19 | 20 | func TestDefault(t *testing.T) { 21 | var b []byte 22 | buf := bytes.NewBuffer(b) 23 | SetWriter(buf) 24 | 25 | debug := Debug("foo") 26 | debug("something") 27 | debug("here") 28 | debug("whoop") 29 | 30 | if buf.Len() != 0 { 31 | t.Fatalf("buffer should be empty") 32 | } 33 | } 34 | 35 | func TestEnable(t *testing.T) { 36 | var b []byte 37 | buf := bytes.NewBuffer(b) 38 | SetWriter(buf) 39 | 40 | Enable("foo") 41 | 42 | debug := Debug("foo") 43 | debug("something") 44 | debug("here") 45 | debug("whoop") 46 | 47 | if buf.Len() == 0 { 48 | t.Fatalf("buffer should have output") 49 | } 50 | 51 | str := string(buf.Bytes()) 52 | assertContains(t, str, "something") 53 | assertContains(t, str, "here") 54 | assertContains(t, str, "whoop") 55 | } 56 | 57 | func TestMultipleOneEnabled(t *testing.T) { 58 | var b []byte 59 | buf := bytes.NewBuffer(b) 60 | SetWriter(buf) 61 | 62 | Enable("foo") 63 | 64 | foo := Debug("foo") 65 | foo("foo") 66 | 67 | bar := Debug("bar") 68 | bar("bar") 69 | 70 | if buf.Len() == 0 { 71 | t.Fatalf("buffer should have output") 72 | } 73 | 74 | str := string(buf.Bytes()) 75 | assertContains(t, str, "foo") 76 | assertNotContains(t, str, "bar") 77 | } 78 | 79 | func TestMultipleEnabled(t *testing.T) { 80 | var b []byte 81 | buf := bytes.NewBuffer(b) 82 | SetWriter(buf) 83 | 84 | Enable("foo,bar") 85 | 86 | foo := Debug("foo") 87 | foo("foo") 88 | 89 | bar := Debug("bar") 90 | bar("bar") 91 | 92 | if buf.Len() == 0 { 93 | t.Fatalf("buffer should have output") 94 | } 95 | 96 | str := string(buf.Bytes()) 97 | assertContains(t, str, "foo") 98 | assertContains(t, str, "bar") 99 | } 100 | 101 | func TestEnableDisable(t *testing.T) { 102 | var b []byte 103 | buf := bytes.NewBuffer(b) 104 | SetWriter(buf) 105 | 106 | Enable("foo,bar") 107 | Disable() 108 | 109 | foo := Debug("foo") 110 | foo("foo") 111 | 112 | bar := Debug("bar") 113 | bar("bar") 114 | 115 | if buf.Len() != 0 { 116 | t.Fatalf("buffer should not have output") 117 | } 118 | } 119 | 120 | func ExampleEnable() { 121 | Enable("mongo:connection") 122 | Enable("mongo:*") 123 | Enable("foo,bar,baz") 124 | Enable("*") 125 | } 126 | 127 | func ExampleDebug() { 128 | var debug = Debug("single") 129 | 130 | for { 131 | debug("sending mail") 132 | debug("send email to %s", "tobi@segment.io") 133 | debug("send email to %s", "loki@segment.io") 134 | debug("send email to %s", "jane@segment.io") 135 | time.Sleep(500 * time.Millisecond) 136 | } 137 | } 138 | 139 | func BenchmarkDisabled(b *testing.B) { 140 | debug := Debug("something") 141 | for i := 0; i < b.N; i++ { 142 | debug("stuff") 143 | } 144 | } 145 | 146 | func BenchmarkNonMatch(b *testing.B) { 147 | debug := Debug("something") 148 | Enable("nonmatch") 149 | for i := 0; i < b.N; i++ { 150 | debug("stuff") 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /example/multiple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import . "github.com/visionmedia/go-debug" 4 | import "time" 5 | 6 | var a = Debug("multiple:a") 7 | var b = Debug("multiple:b") 8 | var c = Debug("multiple:c") 9 | 10 | func work(debug DebugFunction, delay time.Duration) { 11 | for { 12 | debug("doing stuff") 13 | time.Sleep(delay) 14 | } 15 | } 16 | 17 | func main() { 18 | q := make(chan bool) 19 | 20 | go work(a, 1000*time.Millisecond) 21 | go work(b, 250*time.Millisecond) 22 | go work(c, 100*time.Millisecond) 23 | 24 | <-q 25 | } 26 | -------------------------------------------------------------------------------- /example/single.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import . "github.com/visionmedia/go-debug" 4 | import "time" 5 | 6 | var debug = Debug("single") 7 | 8 | func main() { 9 | for { 10 | debug("sending mail") 11 | debug("send email to %s", "tobi@segment.io") 12 | debug("send email to %s", "loki@segment.io") 13 | debug("send email to %s", "jane@segment.io") 14 | time.Sleep(500 * time.Millisecond) 15 | } 16 | } 17 | --------------------------------------------------------------------------------