Skip to content

Commit

Permalink
xlog: Key Values and WithCaller
Browse files Browse the repository at this point in the history
  • Loading branch information
dissoupov committed Jun 23, 2021
1 parent b3b996c commit 46d6096
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 256 deletions.
2 changes: 1 addition & 1 deletion .VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.6
v0.7
155 changes: 142 additions & 13 deletions xlog/formatters.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package xlog

import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"log"
Expand All @@ -26,10 +28,16 @@ import (

// Formatter defines an interface for formatting logs
type Formatter interface {
// Format log entry string to the stream
// Format log entry string to the stream,
// the entries are separated by space
Format(pkg string, level LogLevel, depth int, entries ...interface{})
// FormatKV log entry string to the stream,
// the entries are key/value pairs
FormatKV(pkg string, level LogLevel, depth int, entries ...interface{})
// Flush the logs
Flush()
// WithCaller allows to configure if the caller shall be logged
WithCaller(bool) Formatter
}

// NewStringFormatter returns string-based formatter
Expand All @@ -41,22 +49,42 @@ func NewStringFormatter(w io.Writer) Formatter {

// StringFormatter defines string-based formatter
type StringFormatter struct {
w *bufio.Writer
w *bufio.Writer
withCaller bool
}

// FormatKV log entry string to the stream,
// the entries are key/value pairs
func (s *StringFormatter) FormatKV(pkg string, level LogLevel, depth int, entries ...interface{}) {
s.Format(pkg, level, depth+1, flatten(entries...))
}

// WithCaller allows to configure if the caller shall be logged
func (s *StringFormatter) WithCaller(val bool) Formatter {
s.withCaller = val
return s
}

// Format log entry string to the stream
func (s *StringFormatter) Format(pkg string, l LogLevel, i int, entries ...interface{}) {
func (s *StringFormatter) Format(pkg string, l LogLevel, depth int, entries ...interface{}) {
now := time.Now().UTC()
s.w.WriteString(now.Format(time.RFC3339))
s.w.WriteByte(' ')
writeEntries(s.w, pkg, l, i, entries...)
writeEntries(s.w, pkg, l, depth+1, s.withCaller, entries...)
s.Flush()
}

func writeEntries(w *bufio.Writer, pkg string, _ LogLevel, _ int, entries ...interface{}) {
func writeEntries(w *bufio.Writer, pkg string, _ LogLevel, depth int, withCaller bool, entries ...interface{}) {
if pkg != "" {
w.WriteString(pkg + ": ")
}

if withCaller {
w.WriteString("src=")
w.WriteString(callerName(depth + 1))
w.WriteString(", ")
}

str := fmt.Sprint(entries...)
endsInNL := strings.HasSuffix(str, "\n")
w.WriteString(str)
Expand All @@ -80,8 +108,21 @@ func NewPrettyFormatter(w io.Writer, debug bool) Formatter {

// PrettyFormatter provides default logs format
type PrettyFormatter struct {
w *bufio.Writer
debug bool
w *bufio.Writer
debug bool
withCaller bool
}

// WithCaller allows to configure if the caller shall be logged
func (c *PrettyFormatter) WithCaller(val bool) Formatter {
c.withCaller = val
return c
}

// FormatKV log entry string to the stream,
// the entries are key/value pairs
func (c *PrettyFormatter) FormatKV(pkg string, level LogLevel, depth int, entries ...interface{}) {
c.Format(pkg, level, depth+1, flatten(entries...))
}

// Format log entry string to the stream
Expand All @@ -108,7 +149,7 @@ func (c *PrettyFormatter) Format(pkg string, l LogLevel, depth int, entries ...i
c.w.WriteString(fmt.Sprintf(" [%s:%d]", file, line))
}
c.w.WriteString(fmt.Sprint(" ", l.Char(), " | "))
writeEntries(c.w, pkg, l, depth, entries...)
writeEntries(c.w, pkg, l, depth+1, c.withCaller, entries...)
c.Flush()
}

Expand All @@ -127,8 +168,9 @@ func NewColorFormatter(w io.Writer, color bool) Formatter {

// ColorFormatter provides colorful logs format
type ColorFormatter struct {
w *bufio.Writer
color bool
w *bufio.Writer
color bool
withCaller bool
}

// color pallete map
Expand Down Expand Up @@ -159,6 +201,18 @@ var LevelColors = map[LogLevel][]byte{
TRACE: colorGray,
}

// WithCaller allows to configure if the caller shall be logged
func (c *ColorFormatter) WithCaller(val bool) Formatter {
c.withCaller = val
return c
}

// FormatKV log entry string to the stream,
// the entries are key/value pairs
func (c *ColorFormatter) FormatKV(pkg string, level LogLevel, depth int, entries ...interface{}) {
c.Format(pkg, level, depth+1, flatten(entries...))
}

// Format log entry string to the stream
func (c *ColorFormatter) Format(pkg string, l LogLevel, depth int, entries ...interface{}) {
now := time.Now()
Expand All @@ -170,7 +224,7 @@ func (c *ColorFormatter) Format(pkg string, l LogLevel, depth int, entries ...in
c.w.Write(LevelColors[l])
}
c.w.WriteString(fmt.Sprint(" ", l.Char(), " | "))
writeEntries(c.w, pkg, l, depth, entries...)
writeEntries(c.w, pkg, l, depth+1, c.withCaller, entries...)
if c.color {
c.w.Write(ColorOff)
}
Expand All @@ -184,8 +238,9 @@ func (c *ColorFormatter) Flush() {

// LogFormatter emulates the form of the traditional built-in logger.
type LogFormatter struct {
logger *log.Logger
prefix string
logger *log.Logger
prefix string
withCaller bool
}

// NewLogFormatter is a helper to produce a new LogFormatter struct. It uses the
Expand All @@ -197,6 +252,18 @@ func NewLogFormatter(w io.Writer, prefix string, flag int) Formatter {
}
}

// WithCaller allows to configure if the caller shall be logged
func (lf *LogFormatter) WithCaller(val bool) Formatter {
lf.withCaller = val
return lf
}

// FormatKV log entry string to the stream,
// the entries are key/value pairs
func (lf *LogFormatter) FormatKV(pkg string, level LogLevel, depth int, entries ...interface{}) {
lf.Format(pkg, level, depth+1, flatten(entries...))
}

// Format builds a log message for the LogFormatter. The LogLevel is ignored.
func (lf *LogFormatter) Format(pkg string, _ LogLevel, _ int, entries ...interface{}) {
str := fmt.Sprint(entries...)
Expand All @@ -222,6 +289,16 @@ func NewNilFormatter() Formatter {
return &NilFormatter{}
}

// WithCaller allows to configure if the caller shall be logged
func (c *NilFormatter) WithCaller(val bool) Formatter {
return c
}

// FormatKV log entry string to the stream,
// the entries are key/value pairs
func (*NilFormatter) FormatKV(pkg string, level LogLevel, depth int, entries ...interface{}) {
}

// Format does nothing.
func (*NilFormatter) Format(_ string, _ LogLevel, _ int, _ ...interface{}) {
// noop
Expand All @@ -231,3 +308,55 @@ func (*NilFormatter) Format(_ string, _ LogLevel, _ int, _ ...interface{}) {
func (*NilFormatter) Flush() {
// noop
}

func flatten(kvList ...interface{}) string {
size := len(kvList)
buf := bytes.Buffer{}
for i := 0; i < size; i += 2 {
k, ok := kvList[i].(string)
if !ok {
panic(fmt.Sprintf("key is not a string: %s", pretty(kvList[i])))
}
var v interface{}
if i+1 < size {
v = kvList[i+1]
}
if i > 0 {
buf.WriteRune(',')
buf.WriteRune(' ')
}
buf.WriteString(k)
buf.WriteString("=")
buf.WriteString(pretty(v))

}
return buf.String()
}

func pretty(value interface{}) string {
if err, ok := value.(error); ok {
if _, ok := value.(json.Marshaler); !ok {
value = err.Error()
}
}
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
encoder.Encode(value)
return strings.TrimSpace(buffer.String())
}

func callerName(depth int) string {
pc, _, _, ok := runtime.Caller(depth)
details := runtime.FuncForPC(pc)
if ok && details != nil {
name := details.Name()

idx := strings.LastIndex(name, ".")
if idx >= 0 {
name = name[idx+1:]
}
return name
}
return "n/a"
}
5 changes: 1 addition & 4 deletions xlog/log_hijack.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ type packageWriter struct {
}

func (p packageWriter) Write(b []byte) (int, error) {
if p.pl.level < INFO {
return 0, nil
}
p.pl.internalLog(calldepth+2, INFO, string(b))
p.pl.internalLog(plain, calldepth+2, INFO, string(b))
return len(b), nil
}
10 changes: 5 additions & 5 deletions xlog/logmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,23 +103,23 @@ func Test_SetLevel(t *testing.T) {
}

func Test_GetRepoLogger(t *testing.T) {
r, err := xlog.GetRepoLogger("repo1")
_, err := xlog.GetRepoLogger("repo1")
require.Error(t, err)
assert.Equal(t, "no packages registered for repo: repo1", err.Error())

logger1 := xlog.NewPackageLogger("repo1", "pkg1")
r, err = xlog.GetRepoLogger("repo1")
_, err = xlog.GetRepoLogger("repo1")
require.NoError(t, err)
logger1.Println("repo1", "pkg1")
logger1.KV(xlog.INFO, "repo1", "pkg1")

logger2 := xlog.NewPackageLogger("repo2", "pkg2")
xlog.NewPackageLogger("repo2", "pkg3")
r = xlog.MustRepoLogger("repo2")
r := xlog.MustRepoLogger("repo2")
r.SetLogLevel(map[string]xlog.LogLevel{
"*": xlog.TRACE,
"pkg2": xlog.DEBUG,
})
logger2.Println("repo1", "pkg1")
logger2.KV(xlog.INFO, "repo1", "pkg1")

mm, err := r.ParseLogLevelConfig("pkg2=N,pkg3=DEBUG")
require.NoError(t, err)
Expand Down
4 changes: 1 addition & 3 deletions xlog/logrotate/logrotate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,13 @@ func Test_Rotate(t *testing.T) {
logger.Debugf("%d", 2)
logger.Info("1")
logger.Infof("%d", 2)
logger.KV(xlog.INFO, "k", 2)
logger.Error("1")
logger.Errorf("%d", 2)
logger.Trace("1")
logger.Tracef("%d", 2)
logger.Notice("1")
logger.Noticef("%d", 2)
logger.Print("1")
logger.Println("1")
logger.Printf("%d", 2)

writer.Flush()
assert.NotEmpty(t, b.Bytes())
Expand Down
18 changes: 9 additions & 9 deletions xlog/nillogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,6 @@ func NewNilLogger() Logger {
return &NilLogger{}
}

// Print does nothing
func (l *NilLogger) Print(args ...interface{}) {}

// Printf does nothing
func (l *NilLogger) Printf(format string, args ...interface{}) {}

// Println does nothing
func (l *NilLogger) Println(args ...interface{}) {}

// Fatal does nothing
func (l *NilLogger) Fatal(args ...interface{}) {}

Expand All @@ -61,6 +52,9 @@ func (l *NilLogger) Info(entries ...interface{}) {}
// Infof does nothing
func (l *NilLogger) Infof(format string, args ...interface{}) {}

// KV does nothing
func (l *NilLogger) KV(_ LogLevel, entries ...interface{}) {}

// Error does nothing
func (l *NilLogger) Error(entries ...interface{}) {}

Expand Down Expand Up @@ -90,3 +84,9 @@ func (l *NilLogger) Trace(entries ...interface{}) {}

// Tracef does nothing
func (l *NilLogger) Tracef(format string, args ...interface{}) {}

// WithValues adds some key-value pairs of context to a logger.
// See Info for documentation on how key/value pairs work.
func (l *NilLogger) WithValues(keysAndValues ...interface{}) Logger {
return l
}
3 changes: 0 additions & 3 deletions xlog/nillogger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ func Test_NewNilLogger(t *testing.T) {
logger.Tracef("%d", 2)
logger.Notice("1")
logger.Noticef("%d", 2)
logger.Print("1")
logger.Println("1")
logger.Printf("%d", 2)

assert.Empty(t, b.Bytes())
}
Loading

0 comments on commit 46d6096

Please sign in to comment.