Skip to content

Commit bfacf9d

Browse files
committed
Initial commit
0 parents  commit bfacf9d

File tree

7 files changed

+425
-0
lines changed

7 files changed

+425
-0
lines changed

History.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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

Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
test:
3+
@go test
4+
5+
bench:
6+
@go test -bench=.
7+
8+
.PHONY: bench test

Readme.md

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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", "[email protected]")
28+
debug("send email to %s", "[email protected]")
29+
debug("send email to %s", "[email protected]")
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 [email protected]
40+
15:58:15.116 1us 1us single - send email to [email protected]
41+
15:58:15.116 1us 1us single - send email to [email protected]
42+
15:58:15.620 504ms 504ms single - sending mail
43+
15:58:15.620 6us 6us single - send email to [email protected]
44+
15:58:15.620 4us 4us single - send email to [email protected]
45+
15:58:15.620 4us 4us single - send email to [email protected]
46+
15:58:16.123 503ms 503ms single - sending mail
47+
15:58:16.123 7us 7us single - send email to [email protected]
48+
15:58:16.123 4us 4us single - send email to [email protected]
49+
15:58:16.123 4us 4us single - send email to [email protected]
50+
15:58:16.625 501ms 501ms single - sending mail
51+
15:58:16.625 4us 4us single - send email to [email protected]
52+
15:58:16.625 4us 4us single - send email to [email protected]
53+
15:58:16.625 5us 5us single - send email to [email protected]
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

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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+
}

debug_test.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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", "[email protected]")
133+
debug("send email to %s", "[email protected]")
134+
debug("send email to %s", "[email protected]")
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+
}

0 commit comments

Comments
 (0)