Skip to content

Commit bd49c7d

Browse files
committedDec 15, 2020
Thread Safetey appears to be working with immutable logic added
1 parent 7254d58 commit bd49c7d

6 files changed

+140
-64
lines changed
 

‎debug.go

+31-52
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type Debugger struct {
3838
prev time.Time
3939
fields Fields
4040
color string
41-
mu MutexWrap
41+
// mu MutexWrap
4242
}
4343

4444
type IDebugger interface {
@@ -223,35 +223,39 @@ var SetHasTime = setBoolWithLock(func(isOn bool) {
223223
HAS_TIME = isOn
224224
})
225225

226+
func (dbg Debugger) ApplyRootFields(name string) Debugger {
227+
if formatter.GetHasFieldsOnly() {
228+
dbg = *dbg.WithFields(map[string]interface{}{"namespace": name})
229+
230+
if HAS_TIME {
231+
dbg = *dbg.WithFields(map[string]interface{}{"time": nil, "delta": nil})
232+
}
233+
}
234+
return dbg
235+
}
236+
226237
// Debug creates a debug function for `name` which you call
227238
// with printf-style arguments in your application or library.
228-
func Debug(name string) *Debugger {
239+
func Debug(name string) Debugger {
229240
entry, cached := cache.Get(name)
230241

231242
if cached {
232243
dbg, _ := entry.(Debugger)
233-
return &dbg
244+
return dbg.ApplyRootFields(name)
234245
}
235246

236247
dbg := Debugger{name: name, prev: time.Now(), color: colors[rand.Intn(len(colors))]}
237-
238-
if formatter.GetHasFieldsOnly() {
239-
dbg.WithFields(map[string]interface{}{"namespace": name})
240-
241-
if HAS_TIME {
242-
dbg.WithFields(map[string]interface{}{"time": nil, "delta": nil})
243-
}
244-
}
248+
dbg = dbg.ApplyRootFields(name)
245249

246250
cache.Set(name, dbg, goCache.DefaultExpiration)
247251

248-
return &dbg
252+
return dbg
249253
}
250254

251-
func (dbg *Debugger) Spawn(ns string) *Debugger {
255+
func (dbg Debugger) Spawn(ns string) *Debugger {
252256
d := Debug(dbg.name + ":" + ns)
253257
d.fields = dbg.fields
254-
return d
258+
return &d
255259
}
256260

257261
func (dbg *Debugger) Log(args ...interface{}) {
@@ -269,8 +273,7 @@ func (dbg *Debugger) Log(args ...interface{}) {
269273
}
270274
}
271275

272-
dbg.mu.Lock()
273-
276+
// dbg.mu.Lock()
274277
var msg interface{}
275278

276279
if len(args) >= 1 {
@@ -296,49 +299,46 @@ func (dbg *Debugger) Log(args ...interface{}) {
296299
}
297300
}
298301

299-
preppedMsg := formatter.Format(dbg, msg)
300-
dbg.mu.Unlock()
301-
302+
m.Lock()
303+
preppedMsg := formatter.Format(*dbg, msg)
302304
fmt.Fprintf(writer, preppedMsg, args...)
305+
m.Unlock()
303306
dbg.prev = time.Now()
304307
}
305308

306309
// prepend error/warn name as it is easier to filter!
307-
func (dbg *Debugger) Error(args ...interface{}) {
310+
func (dbg Debugger) Error(args ...interface{}) {
308311
d := Debug("error:" + dbg.name)
309312
d.fields = dbg.fields
310313
d.Log(args...)
311314
}
312-
func (dbg *Debugger) Warn(args ...interface{}) {
315+
func (dbg Debugger) Warn(args ...interface{}) {
313316
d := Debug("warn:" + dbg.name)
314317
d.fields = dbg.fields
315318
d.Log(args...)
316319
}
317320

318-
func (dbg *Debugger) WithFields(fields map[string]interface{}) *Debugger {
319-
dbg.mu.Lock()
321+
// NOT *Debugger receiver on purpose to be immutable to not deal with locks on fields
322+
func (dbg Debugger) WithFields(fields map[string]interface{}) *Debugger {
320323
if len(dbg.fields) == 0 {
321324
dbg.fields = fields
322-
dbg.mu.Unlock()
323-
return dbg
325+
return &dbg
324326
}
325327

326328
for k, v := range fields {
327329
dbg.fields[k] = v
328330
}
329-
dbg.mu.Unlock()
330-
return dbg
331+
return &dbg
331332
}
332333

333-
func (dbg *Debugger) WithField(key string, value interface{}) *Debugger {
334-
dbg.mu.Lock()
334+
// NOT *Debugger receiver on purpose to be immutable to not deal with locks on fields
335+
func (dbg Debugger) WithField(key string, value interface{}) *Debugger {
335336
if len(dbg.fields) == 0 {
336337
dbg.fields = map[string]interface{}{}
337338
}
338339

339340
dbg.fields[key] = value
340-
dbg.mu.Unlock()
341-
return dbg
341+
return &dbg
342342
}
343343

344344
func getColorStr(color string, isOn bool) string {
@@ -391,24 +391,3 @@ func humanizeNano(n int64) string {
391391

392392
return strconv.Itoa(int(n)) + suffix
393393
}
394-
395-
type MutexWrap struct {
396-
lock sync.Mutex
397-
disabled bool
398-
}
399-
400-
func (mw *MutexWrap) Lock() {
401-
if !mw.disabled {
402-
mw.lock.Lock()
403-
}
404-
}
405-
406-
func (mw *MutexWrap) Unlock() {
407-
if !mw.disabled {
408-
mw.lock.Unlock()
409-
}
410-
}
411-
412-
func (mw *MutexWrap) Disable() {
413-
mw.disabled = true
414-
}

‎debug_thread_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package debug
2+
3+
import (
4+
"bytes"
5+
"strconv"
6+
"sync"
7+
"testing"
8+
)
9+
10+
func TestThreadSafety(t *testing.T) {
11+
debug := Debug("foo")
12+
var b []byte
13+
buf := bytes.NewBuffer(b)
14+
SetWriter(buf)
15+
Enable("*")
16+
17+
wg := sync.WaitGroup{}
18+
totalThreads := 8000
19+
wg.Add(totalThreads)
20+
21+
for i := 0; i < totalThreads; i++ {
22+
i := i
23+
go func() {
24+
defer wg.Done()
25+
f := Fields{}
26+
f[strconv.Itoa(i)] = "test"
27+
debug.Spawn("thread" + strconv.Itoa(i)).WithFields(f).Log("something")
28+
}()
29+
}
30+
wg.Wait()
31+
}
32+
33+
func TestThreadJsonSafety(t *testing.T) {
34+
debug := Debug("foo")
35+
SetFormatter(&JSONFormatter{})
36+
var b []byte
37+
buf := bytes.NewBuffer(b)
38+
SetWriter(buf)
39+
Enable("*")
40+
41+
wg := sync.WaitGroup{}
42+
totalThreads := 1000
43+
wg.Add(totalThreads)
44+
45+
for i := 0; i < totalThreads; i++ {
46+
i := i
47+
go func() {
48+
defer wg.Done()
49+
f := Fields{}
50+
f[strconv.Itoa(i)] = "test"
51+
f["a"] = "test"
52+
f["b"] = "test"
53+
f["c"] = "test"
54+
f["d"] = "test"
55+
debug.Spawn("thread" + strconv.Itoa(i)).WithFields(f).Log("something")
56+
}()
57+
}
58+
wg.Wait()
59+
}

‎formatter.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const (
1515

1616
// highly inspired by logrus
1717
type Formatter interface {
18-
Format(*Debugger, interface{}) string
18+
Format(Debugger, interface{}) string
1919
GetHasFieldsOnly() bool
2020
}
2121

@@ -28,7 +28,7 @@ type TextFormatter struct {
2828
SortingFunc func(keys []string)
2929
}
3030

31-
func (t *TextFormatter) Format(dbg *Debugger, _msg interface{}) string {
31+
func (t *TextFormatter) Format(dbg Debugger, _msg interface{}) string {
3232
msg, didCast := _msg.(string)
3333
if !didCast {
3434
msg = fmt.Sprintf("%+v", _msg)
@@ -132,7 +132,7 @@ type Finalized struct {
132132
}
133133

134134
func finalizeFields(
135-
dbg *Debugger, msg string, hasColor bool, flattenMsg bool, cb func(string, interface{}) interface{}) *Finalized {
135+
dbg Debugger, msg string, hasColor bool, flattenMsg bool, cb func(string, interface{}) interface{}) *Finalized {
136136
ts, delta := deltas(dbg.prev)
137137
ns := getColorStr(dbg.color, hasColor) + dbg.name + getColorOff(hasColor)
138138

‎formatter_test.go

+39-1
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import (
88
"github.com/stretchr/testify/assert"
99
)
1010

11-
func TestTextFormatterFieldSpawnMultipleEnabled(t *testing.T) {
11+
func TestTextFormatterFieldSpawnMultipleEnabledText(t *testing.T) {
1212
var b []byte
1313
buf := bytes.NewBuffer(b)
1414
SetWriter(buf)
15+
SetFormatterString("text")
1516

1617
Enable("foo*,bar*")
1718

@@ -40,10 +41,47 @@ func TestTextFormatterFieldSpawnMultipleEnabled(t *testing.T) {
4041
assert.Contains(t, str, "bar lazy")
4142
}
4243

44+
func TestTextFormatterFieldSpawnMultipleEnabledJSON(t *testing.T) {
45+
var b []byte
46+
buf := bytes.NewBuffer(b)
47+
SetWriter(buf)
48+
SetFormatterString("json")
49+
50+
Enable("foo*,bar*")
51+
52+
foo := Debug("foo").Spawn("child").Spawn("grandChild").WithFields(
53+
Fields{"field": 1, "field2": "two", "field3": "multiple strings"})
54+
foo.Log("foo")
55+
foo.Log(func() string { return "foo lazy" })
56+
57+
bar := Debug("bar").Spawn("child").Spawn("grandChild")
58+
bar.Log("bar")
59+
bar.Log(func() string { return "bar lazy" })
60+
61+
if buf.Len() == 0 {
62+
t.Fatalf("buffer should have output")
63+
}
64+
65+
str := buf.String()
66+
// fmt.Println(str)
67+
assert.Contains(t, str, `"field": 1`)
68+
assert.Contains(t, str, `"field2": "two"`)
69+
assert.Contains(t, str, `"field3": "multiple strings"`)
70+
71+
assert.Contains(t, str, `"msg": "foo"`)
72+
assert.Contains(t, str, `"msg": "foo lazy"`)
73+
assert.Contains(t, str, `"msg": "bar"`)
74+
assert.Contains(t, str, `"msg": "bar lazy"`)
75+
// namespaces exist
76+
assert.Contains(t, str, "foo:child:grandChild", "namespace")
77+
// assert.Contains(t, str, "bar:child:grandChild")
78+
}
79+
4380
func TestBasicTextFormatterFieldsStrict(t *testing.T) {
4481
var b []byte
4582
buf := bytes.NewBuffer(b)
4683
SetWriter(buf)
84+
SetFormatterString("text")
4785

4886
Enable("foo*")
4987

‎json_formatter.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type JSONFormatter struct {
4545
FlattenMsgFields bool
4646
}
4747

48-
func (f *JSONFormatter) Format(dbg *Debugger, _msg interface{}) string {
48+
func (f *JSONFormatter) Format(dbg Debugger, _msg interface{}) string {
4949
var msg string
5050
var msgFields *Fields
5151

‎json_formatter_test.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func TestErrorNotLost(t *testing.T) {
1313
SetHasColors(false)
1414
SetFormatter(&JSONFormatter{})
1515

16-
s := formatter.Format(Debug("error_not_lost").WithField("error", errors.New("wild walrus")), "hi")
16+
s := formatter.Format(*Debug("error_not_lost").WithField("error", errors.New("wild walrus")), "hi")
1717

1818
entry := make(map[string]interface{})
1919
err := json.Unmarshal([]byte(s), &entry)
@@ -31,7 +31,7 @@ func TestErrorNotLostOnFieldNotNamedError(t *testing.T) {
3131
SetHasColors(false)
3232
SetFormatter(&JSONFormatter{})
3333

34-
s := formatter.Format(Debug("mapped_field_error").WithField("omg", errors.New("wild walrus")), "hi")
34+
s := formatter.Format(*Debug("mapped_field_error").WithField("omg", errors.New("wild walrus")), "hi")
3535

3636
entry := make(map[string]interface{})
3737
err := json.Unmarshal([]byte(s), &entry)
@@ -49,7 +49,7 @@ func TestFieldClashWithTime(t *testing.T) {
4949
SetHasColors(false)
5050
SetFormatter(&JSONFormatter{})
5151

52-
s := formatter.Format(Debug("clash_time").WithField("time", "right now!"), "hi")
52+
s := formatter.Format(*Debug("clash_time").WithField("time", "right now!"), "hi")
5353

5454
entry := make(map[string]interface{})
5555
err := json.Unmarshal([]byte(s), &entry)
@@ -66,7 +66,7 @@ func TestFieldClashWithMsg(t *testing.T) {
6666
SetHasColors(false)
6767
SetFormatter(&JSONFormatter{})
6868

69-
s := formatter.Format(Debug("clash_msg").WithField("msg", errors.New("wild walrus")), "hi")
69+
s := formatter.Format(*Debug("clash_msg").WithField("msg", errors.New("wild walrus")), "hi")
7070

7171
entry := make(map[string]interface{})
7272
err := json.Unmarshal([]byte(s), &entry)
@@ -79,7 +79,7 @@ func TestFieldClashWithNamespace(t *testing.T) {
7979
SetHasColors(false)
8080
SetFormatter(&JSONFormatter{})
8181

82-
s := formatter.Format(Debug("clash_namespace").WithField("namespace", errors.New("wild walrus")), "hi")
82+
s := formatter.Format(*Debug("clash_namespace").WithField("namespace", errors.New("wild walrus")), "hi")
8383

8484
entry := make(map[string]interface{})
8585
err := json.Unmarshal([]byte(s), &entry)
@@ -91,7 +91,7 @@ func TestFieldClashWithNamespace(t *testing.T) {
9191
func TestJSONEntryEndsWithNewline(t *testing.T) {
9292
SetFormatter(&JSONFormatter{})
9393

94-
s := formatter.Format(Debug("newline").WithField("dog", errors.New("wild walrus")), "hi")
94+
s := formatter.Format(*Debug("newline").WithField("dog", errors.New("wild walrus")), "hi")
9595

9696
entry := make(map[string]interface{})
9797
err := json.Unmarshal([]byte(s), &entry)
@@ -105,7 +105,7 @@ func TestJSONPretty(t *testing.T) {
105105
SetHasColors(false)
106106
SetFormatter(&JSONFormatter{PrettyPrint: true})
107107

108-
s := formatter.Format(Debug("pretty").WithField("dog", "wild walrus"), "hi")
108+
s := formatter.Format(*Debug("pretty").WithField("dog", "wild walrus"), "hi")
109109
expected := "{\n \"dog\": \"wild walrus\",\n \"msg\": \"hi\",\n \"namespace\": \"pretty\"\n}\n"
110110

111111
assert.Equal(t, expected, s, "is pretty")

0 commit comments

Comments
 (0)
Please sign in to comment.