Skip to content

Commit 16e5bf1

Browse files
committed
feat: add json support
1 parent e4a44ba commit 16e5bf1

File tree

4 files changed

+90
-51
lines changed

4 files changed

+90
-51
lines changed

debugo.go

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package debugo
22

33
import (
4-
"fmt"
54
"io"
6-
"maps"
75
"sync"
86
"time"
97

@@ -22,42 +20,24 @@ type Debugger struct {
2220

2321
output io.Writer
2422

25-
fields map[string]any
26-
2723
mutex *sync.Mutex
2824
}
2925

3026
func New(namespace string) *Debugger {
3127
return newDebugger(namespace)
3228
}
3329

30+
// Extend creates a new debugger instance with an extended namespace
3431
func (d *Debugger) Extend(namespace string) *Debugger {
3532
d.mutex.Lock()
3633
defer d.mutex.Unlock()
3734

38-
n := newDebugger(d.namespace + ":" + namespace)
39-
n.color = d.color
40-
n.lastLog = d.lastLog
41-
n.output = d.output
42-
n.mutex = d.mutex
43-
return n
44-
}
45-
46-
func (d *Debugger) With(kv ...any) *Debugger {
47-
d.mutex.Lock()
48-
defer d.mutex.Unlock()
49-
5035
n := *d
51-
maps.Copy(n.fields, d.fields)
52-
53-
for i := 0; i+1 < len(kv); i += 2 {
54-
key := fmt.Sprint(kv[i]) // ensures key is always a string
55-
n.fields[key] = kv[i+1]
56-
}
57-
36+
n.namespace = d.namespace + ":" + namespace
5837
return &n
5938
}
6039

40+
// SetOutput sets the output writer for the debugger instance
6141
func (d *Debugger) SetOutput(output io.Writer) {
6242
d.mutex.Lock()
6343
defer d.mutex.Unlock()
@@ -74,7 +54,5 @@ func newDebugger(namespace string) *Debugger {
7454

7555
output: nil,
7656

77-
fields: make(map[string]any),
78-
7957
mutex: &sync.Mutex{}}
8058
}

runtime.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import (
66
"sync"
77
)
88

9+
type Format string
10+
11+
const (
12+
Plain Format = "plain"
13+
JSON Format = "json"
14+
)
15+
916
type config struct {
1017
namespace string
1118
timestamp *Timestamp
@@ -14,6 +21,8 @@ type config struct {
1421

1522
useColors bool
1623

24+
format Format
25+
1726
mutex *sync.RWMutex
1827
}
1928

@@ -25,13 +34,31 @@ var runtime = &config{
2534

2635
useColors: true,
2736

37+
format: Plain,
38+
2839
mutex: &sync.RWMutex{},
2940
}
3041

3142
type Timestamp struct {
3243
Format string
3344
}
3445

46+
// SetFormat sets the global output format for debugging.
47+
func SetFormat(format Format) {
48+
runtime.mutex.Lock()
49+
defer runtime.mutex.Unlock()
50+
51+
runtime.format = format
52+
}
53+
54+
// GetFormat retrieves the current global output format for debugging.
55+
func GetFormat() Format {
56+
runtime.mutex.RLock()
57+
defer runtime.mutex.RUnlock()
58+
59+
return runtime.format
60+
}
61+
3562
// SetUseColors sets the global color usage for debugging.
3663
func SetUseColors(use bool) {
3764
runtime.mutex.Lock()

write.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
package debugo
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"strings"
67
"time"
78
)
89

10+
type asJSON struct {
11+
Timestamp string `json:"timestamp,omitempty"`
12+
Namespace string `json:"namespace,omitempty"`
13+
Fields map[string]any `json:"fields,omitempty"`
14+
Message string `json:"message,omitempty"`
15+
ElapsedMs int64 `json:"elapsed_ms,omitempty"`
16+
}
17+
918
func (d *Debugger) Debug(message ...any) *Debugger {
1019
d.write(message...)
1120
return d
@@ -29,6 +38,11 @@ func (d *Debugger) write(message ...any) {
2938
return
3039
}
3140

41+
if GetFormat() == JSON {
42+
d.writeJSON(message...)
43+
return
44+
}
45+
3246
// Optional timestamp
3347
var timestamp string
3448
if t := GetTimestamp(); t != nil {
@@ -47,10 +61,6 @@ func (d *Debugger) write(message ...any) {
4761
parts = append(parts, d.namespace)
4862
}
4963

50-
if f := d.formatFields(); f != "" {
51-
parts = append(parts, f)
52-
}
53-
5464
parts = append(parts, msg)
5565
parts = append(parts, fmt.Sprintf("+%s", prettyPrintDuration(d.elapsed())))
5666

@@ -64,15 +74,25 @@ func (d *Debugger) write(message ...any) {
6474
}
6575
}
6676

67-
func (d *Debugger) formatFields() string {
68-
if len(d.fields) == 0 {
69-
return ""
77+
func (d *Debugger) writeJSON(message ...any) {
78+
entry := asJSON{
79+
Namespace: d.namespace,
80+
Message: fmt.Sprint(message...),
81+
ElapsedMs: d.elapsed().Milliseconds(),
7082
}
7183

72-
parts := make([]string, 0, len(d.fields))
73-
for k, v := range d.fields {
74-
parts = append(parts, fmt.Sprintf("%s=%v", k, v))
84+
if t := GetTimestamp(); t != nil {
85+
entry.Timestamp = time.Now().Format(t.Format)
86+
}
87+
88+
data, err := json.Marshal(entry)
89+
if err != nil {
90+
return // fail silently
7591
}
7692

77-
return strings.Join(parts, " ")
93+
if d.output != nil {
94+
_, _ = d.output.Write(append(data, '\n'))
95+
} else if o := GetOutput(); o != nil {
96+
_, _ = o.Write(append(data, '\n'))
97+
}
7898
}

write_test.go

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,27 +42,41 @@ func TestDebug(t *testing.T) {
4242

4343
d.Debug(testMessage)
4444

45-
assert.True(t, hasANSI(buf.String()), "Must have no colors")
45+
assert.True(t, hasANSI(buf.String()))
4646

4747
output := strings.TrimSpace(stripANSI(buf.String())) // Strip colors and trim whitespace
4848
expected := strings.TrimSpace(testMessageExpected)
49-
assert.Equal(t, expected, output, "Must have no colors")
49+
assert.Equal(t, expected, output)
5050
}
5151

52-
func TestDebugWithFields(t *testing.T) {
52+
func TestDebugJSON(t *testing.T) {
5353
var buf bytes.Buffer
5454
d := getDebugger()
5555
d.SetOutput(&buf)
56+
SetFormat(JSON)
57+
d.Debug(testMessage)
58+
SetFormat(Plain)
5659

57-
x := d.With("key1", "value1").With("key2", 42)
58-
59-
x.Debug(testMessage)
60+
assert.False(t, hasANSI(buf.String()))
61+
output := strings.TrimSpace(stripANSI(buf.String())) // Strip colors and trim whitespace
62+
expected := strings.TrimSpace("{\"namespace\":\"" + namespace + "\",\"message\":\"" + testMessage + "\"}")
63+
assert.Equal(t, expected, output)
64+
}
6065

61-
assert.True(t, hasANSI(buf.String()), "Must have no colors")
66+
func TestDebugJSONWithTimestamp(t *testing.T) {
67+
var buf bytes.Buffer
68+
d := getDebugger()
69+
SetOutput(&buf)
70+
SetTimestamp(&Timestamp{Format: "2006"})
71+
SetFormat(JSON)
72+
d.Debug(testMessage)
73+
SetFormat(Plain)
74+
SetTimestamp(nil)
6275

76+
assert.False(t, hasANSI(buf.String()))
6377
output := strings.TrimSpace(stripANSI(buf.String())) // Strip colors and trim whitespace
64-
expected := strings.TrimSpace(fmt.Sprintf("%s key1=value1 key2=42 %s +0ms\n", namespace, testMessage))
65-
assert.Equal(t, expected, output, "Must have fields")
78+
expected := strings.TrimSpace("{\"timestamp\":\"" + fmt.Sprint(time.Now().Year()) + "\",\"namespace\":\"" + namespace + "\",\"message\":\"" + testMessage + "\"}")
79+
assert.Equal(t, expected, output)
6680
}
6781

6882
func TestDebugGlobalOutput(t *testing.T) {
@@ -74,11 +88,11 @@ func TestDebugGlobalOutput(t *testing.T) {
7488

7589
d.Debug(testMessage)
7690

77-
assert.True(t, hasANSI(buf.String()), "Must have no colors")
91+
assert.True(t, hasANSI(buf.String()))
7892

7993
output := strings.TrimSpace(stripANSI(buf.String())) // Strip colors and trim whitespace
8094
expected := strings.TrimSpace(testMessageExpected)
81-
assert.Equal(t, expected, output, "Must have no colors")
95+
assert.Equal(t, expected, output)
8296
}
8397

8498
func TestDebugNoColors(t *testing.T) {
@@ -89,7 +103,7 @@ func TestDebugNoColors(t *testing.T) {
89103

90104
d.Debug(testMessage)
91105

92-
assert.False(t, hasANSI(buf.String()), "Must have no colors")
106+
assert.False(t, hasANSI(buf.String()))
93107
}
94108

95109
func TestDebugNonMatchingNamespace(t *testing.T) {
@@ -100,7 +114,7 @@ func TestDebugNonMatchingNamespace(t *testing.T) {
100114

101115
d.Debug("")
102116

103-
assert.Empty(t, buf.String(), "Must have no message")
117+
assert.Empty(t, buf.String())
104118
}
105119

106120
func TestDebugEmptyMessage(t *testing.T) {
@@ -112,7 +126,7 @@ func TestDebugEmptyMessage(t *testing.T) {
112126
SetNamespace("does:not:exist")
113127
d.Debug("test")
114128

115-
assert.Empty(t, buf.String(), "Must have no message")
129+
assert.Empty(t, buf.String())
116130
}
117131

118132
func TestDebugWithColors(t *testing.T) {
@@ -123,7 +137,7 @@ func TestDebugWithColors(t *testing.T) {
123137

124138
d.Debug(testMessage)
125139

126-
assert.True(t, hasANSI(buf.String()), "Must have colors")
140+
assert.True(t, hasANSI(buf.String()))
127141
}
128142

129143
func TestDebugf(t *testing.T) {

0 commit comments

Comments
 (0)