Skip to content

Commit

Permalink
feat: export context key (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Aug 1, 2023
1 parent 280c4e3 commit 0dae082
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 38 deletions.
5 changes: 3 additions & 2 deletions json.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package log
import (
"encoding/json"
"fmt"
"io"
"time"
)

func (l *Logger) jsonFormatter(keyvals ...interface{}) {
func (l *Logger) jsonFormatter(w io.Writer, keyvals ...interface{}) {
m := make(map[string]interface{}, len(keyvals)/2)
for i := 0; i < len(keyvals); i += 2 {
switch keyvals[i] {
Expand Down Expand Up @@ -55,7 +56,7 @@ func (l *Logger) jsonFormatter(keyvals ...interface{}) {
}
}

e := json.NewEncoder(&l.b)
e := json.NewEncoder(w)
e.SetEscapeHTML(false)
_ = e.Encode(m)
}
6 changes: 3 additions & 3 deletions logfmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package log
import (
"errors"
"fmt"
"io"
"time"

"github.com/go-logfmt/logfmt"
)

func (l *Logger) logfmtFormatter(keyvals ...interface{}) {
e := logfmt.NewEncoder(&l.b)

func (l *Logger) logfmtFormatter(w io.Writer, keyvals ...interface{}) {
e := logfmt.NewEncoder(w)
for i := 0; i < len(keyvals); i += 2 {
switch keyvals[i] {
case TimestampKey:
Expand Down
23 changes: 16 additions & 7 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ type LoggerOption = func(*Logger)
// Logger is a Logger that implements Logger.
type Logger struct {
w io.Writer
b bytes.Buffer
mu *sync.RWMutex
re *lipgloss.Renderer

Expand All @@ -46,6 +45,8 @@ type Logger struct {
fields []interface{}

helpers *sync.Map

bufPool sync.Pool
}

func (l *Logger) log(level Level, msg interface{}, keyvals ...interface{}) {
Expand All @@ -60,7 +61,6 @@ func (l *Logger) log(level Level, msg interface{}, keyvals ...interface{}) {

l.mu.Lock()
defer l.mu.Unlock()
defer l.b.Reset()

var kvs []interface{}
if l.reportTimestamp {
Expand Down Expand Up @@ -98,16 +98,26 @@ func (l *Logger) log(level Level, msg interface{}, keyvals ...interface{}) {
kvs = append(kvs, ErrMissingValue)
}

b := l.bufPool.Get().(*bytes.Buffer)
switch l.formatter {
case LogfmtFormatter:
l.logfmtFormatter(kvs...)
l.logfmtFormatter(b, kvs...)
case JSONFormatter:
l.jsonFormatter(kvs...)
l.jsonFormatter(b, kvs...)
default:
l.textFormatter(kvs...)
l.textFormatter(b, kvs...)
}

_, _ = l.w.Write(l.b.Bytes())
// WriteTo will reset the buffer
b.WriteTo(l.w) // nolint: errcheck
}

func newBufPool() sync.Pool {
return sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
}

// Helper marks the calling function as a helper
Expand Down Expand Up @@ -288,7 +298,6 @@ func (l *Logger) With(keyvals ...interface{}) *Logger {
l.mu.Lock()
sl := *l
l.mu.Unlock()
sl.b = bytes.Buffer{}
sl.mu = &sync.RWMutex{}
sl.helpers = &sync.Map{}
sl.fields = append(l.fields, keyvals...)
Expand Down
37 changes: 37 additions & 0 deletions logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package log

import (
"bytes"
"io"
"sync"
"testing"

Expand Down Expand Up @@ -183,3 +184,39 @@ func TestLogWithRaceCondition(t *testing.T) {
})
}
}

func TestRace2(t *testing.T) {
t.Parallel()
l := New(io.Discard)

for i := 0; i < 200; i++ {
t.Run("race", func(t *testing.T) {
t.Parallel()
l.Printf("foo")
})
}
}

func TestRace(t *testing.T) {
t.Parallel()
l := New(io.Discard)

t.Run("racer", func(t *testing.T) {
t.Parallel()
for i := 0; i < 200; i++ {
t.Run("race", func(t *testing.T) {
t.Parallel()
l.Printf("foo")
l.Error("bar")
l.Debug("baz")
})
}
})

for i := 0; i < 200; i++ {
t.Run("race", func(t *testing.T) {
t.Parallel()
l.Printf("foo")
})
}
}
3 changes: 1 addition & 2 deletions pkg.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package log

import (
"bytes"
"fmt"
"io"
"log"
Expand Down Expand Up @@ -36,7 +35,6 @@ func New(w io.Writer) *Logger {
// NewWithOptions returns a new logger using the provided options.
func NewWithOptions(w io.Writer, o Options) *Logger {
l := &Logger{
b: bytes.Buffer{},
mu: &sync.RWMutex{},
helpers: &sync.Map{},
level: int32(o.Level),
Expand All @@ -49,6 +47,7 @@ func NewWithOptions(w io.Writer, o Options) *Logger {
fields: o.Fields,
callerFormatter: o.CallerFormatter,
callerOffset: o.CallerOffset,
bufPool: newBufPool(),
}

l.SetOutput(w)
Expand Down
2 changes: 2 additions & 0 deletions stdlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ type StandardLogOptions struct {
// can infer log levels from message prefix. Expected prefixes are DEBUG, INFO,
// WARN, ERROR, and ERR.
func (l *Logger) StandardLog(opts ...StandardLogOptions) *log.Logger {
l.mu.Lock()
nl := *l
l.mu.Unlock()
nl.mu = &sync.RWMutex{}
nl.helpers = &sync.Map{}
// The caller stack is
Expand Down
48 changes: 24 additions & 24 deletions text.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,40 +142,40 @@ func needsQuoting(str string) bool {
return false
}

func (l *Logger) textFormatter(keyvals ...interface{}) {
func (l *Logger) textFormatter(w io.Writer, keyvals ...interface{}) {
for i := 0; i < len(keyvals); i += 2 {
switch keyvals[i] {
case TimestampKey:
if t, ok := keyvals[i+1].(time.Time); ok {
ts := t.Format(l.timeFormat)
ts = TimestampStyle.Renderer(l.re).Render(ts)
l.b.WriteString(ts)
l.b.WriteByte(' ')
io.WriteString(w, ts)
w.Write([]byte{' '})
}
case LevelKey:
if level, ok := keyvals[i+1].(Level); ok {
lvl := levelStyle(level).Renderer(l.re).String()
l.b.WriteString(lvl)
l.b.WriteByte(' ')
io.WriteString(w, lvl)
w.Write([]byte{' '})
}
case CallerKey:
if caller, ok := keyvals[i+1].(string); ok {
caller = fmt.Sprintf("<%s>", caller)
caller = CallerStyle.Renderer(l.re).Render(caller)
l.b.WriteString(caller)
l.b.WriteByte(' ')
io.WriteString(w, caller)
w.Write([]byte{' '})
}
case PrefixKey:
if prefix, ok := keyvals[i+1].(string); ok {
prefix = PrefixStyle.Renderer(l.re).Render(prefix + ":")
l.b.WriteString(prefix)
l.b.WriteByte(' ')
io.WriteString(w, prefix)
w.Write([]byte{' '})
}
case MessageKey:
if msg := keyvals[i+1]; msg != nil {
m := fmt.Sprint(msg)
m = MessageStyle.Renderer(l.re).Render(m)
l.b.WriteString(m)
io.WriteString(w, m)
}
default:
sep := separator
Expand Down Expand Up @@ -211,30 +211,30 @@ func (l *Logger) textFormatter(keyvals ...interface{}) {
// in the value string are "normal", like if they
// contain ANSI escape sequences.
if strings.Contains(val, "\n") {
l.b.WriteString("\n ")
l.b.WriteString(key)
l.b.WriteString(sep + "\n")
l.writeIndent(&l.b, val, indentSep, moreKeys, actualKey)
io.WriteString(w, "\n ")
io.WriteString(w, key)
io.WriteString(w, sep+"\n")
l.writeIndent(w, val, indentSep, moreKeys, actualKey)
// If there are more keyvals, separate them with a space.
if moreKeys {
l.b.WriteByte(' ')
w.Write([]byte{' '})
}
} else if !raw && needsQuoting(val) {
l.b.WriteByte(' ')
l.b.WriteString(key)
l.b.WriteString(sep)
l.b.WriteString(valueStyle.Renderer(l.re).Render(fmt.Sprintf(`"%s"`,
w.Write([]byte{' '})
io.WriteString(w, key)
io.WriteString(w, sep)
io.WriteString(w, valueStyle.Renderer(l.re).Render(fmt.Sprintf(`"%s"`,
escapeStringForOutput(val, true))))
} else {
val = valueStyle.Renderer(l.re).Render(val)
l.b.WriteByte(' ')
l.b.WriteString(key)
l.b.WriteString(sep)
l.b.WriteString(val)
w.Write([]byte{' '})
io.WriteString(w, key)
io.WriteString(w, sep)
io.WriteString(w, val)
}
}
}

// Add a newline to the end of the log message.
l.b.WriteByte('\n')
w.Write([]byte{'\n'})
}

0 comments on commit 0dae082

Please sign in to comment.