Skip to content

Commit

Permalink
Mapper (#19)
Browse files Browse the repository at this point in the history
* add support for optional mappers, per level

* lint: suppress false-positive dup detection

* add example of mapper with colors
  • Loading branch information
umputun authored Aug 24, 2020
1 parent eff9fc5 commit 9a141d1
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 7 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ _Without `lgr.Caller*` it will drop `{caller}` part_
- `lgr.Msec` - adds milliseconds to timestamp
- `lgr.Format` - sets a custom template, overwrite all other formatting modifiers.
- `lgr.Secret(secret ...)` - sets list of the secrets to hide from the logging outputs.
- `lgr.Map(mapper)` - sets mapper functions to change elements of the logging output based on levels.

example: `l := lgr.New(lgr.Debug, lgr.Msec)`

Expand Down Expand Up @@ -79,6 +80,28 @@ _Note: formatter (predefined or custom) adds measurable overhead - the cost will
- `FATAL` and send messages to both out and err writers and exit(1)
- `PANIC` does the same as `FATAL` but in addition sends dump of callers and runtime info to err.

### mapper

Elements of the output can be altered with a set of user defined function passed as `lgr.Map` options. Such a mapper changes
the value of an element (i.e. timestamp, level, message, caller) and has separate functions for each level. Note: both level
and messages elements handled by the same function for a given level.

_A typical use-case is to produce colorful output with a user-define colorization library._

example with [fatih/color](https://github.com/fatih/color):

```go
colorizer := lgr.Mapper{
ErrorFunc: func(s string) string { return color.New(color.FgHiRed).Sprint(s) },
WarnFunc: func(s string) string { return color.New(color.FgHiYellow).Sprint(s) },
InfoFunc: func(s string) string { return color.New(color.FgHiWhite).Sprint(s) },
DebugFunc: func(s string) string { return color.New(color.FgWhite).Sprint(s) },
CallerFunc: func(s string) string { return color.New(color.FgBlue).Sprint(s) },
TimeFunc: func(s string) string { return color.New(color.FgCyan).Sprint(s) },
}

logOpts := []lgr.Option{lgr.Msec, lgr.LevelBraces, lgr.Map(colorizer)}
```
### adaptors

`lgr` logger can be converted to `io.Writer` or `*log.Logger`
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ module github.com/go-pkgz/lgr

require github.com/stretchr/testify v1.5.1

go 1.14
go 1.15
28 changes: 22 additions & 6 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type Logger struct {
callerDepth int // how many stack frames to skip, relative to the real (reported) frame
format string // layout template
secrets [][]byte // sub-strings to secrets by matching
mapper Mapper // map (alter) output based on levels

// internal use
now nowFn
Expand Down Expand Up @@ -90,6 +91,7 @@ func New(options ...Option) *Logger {
stdout: os.Stdout,
stderr: os.Stderr,
callerDepth: 0,
mapper: nopMapper,
}
for _, opt := range options {
opt(&res)
Expand Down Expand Up @@ -275,14 +277,14 @@ func (l *Logger) formatWithOptions(elems layout) (res string) {

parts = append(
parts,
orElse(l.msec,
l.mapper.TimeFunc(orElse(l.msec,
func() string { return elems.DT.Format("2006/01/02 15:04:05.000") },
func() string { return elems.DT.Format("2006/01/02 15:04:05") },
),
orElse(l.levelBraces,
)),
l.levelMapper(elems.Level)(orElse(l.levelBraces,
func() string { return `[` + elems.Level + `]` },
func() string { return elems.Level },
),
)),
)

if l.callerFile || l.callerFunc || l.callerPkg {
Expand All @@ -297,10 +299,10 @@ func (l *Logger) formatWithOptions(elems layout) (res string) {
if v := orElse(l.callerPkg, func() string { return elems.CallerPkg }, nothing); v != "" {
callerParts = append(callerParts, v)
}
parts = append(parts, "{"+strings.Join(callerParts, " ")+"}")
parts = append(parts, l.mapper.CallerFunc("{"+strings.Join(callerParts, " ")+"}"))
}

parts = append(parts, elems.Message)
parts = append(parts, l.levelMapper(elems.Level)(elems.Message))
return strings.Join(parts, " ")
}

Expand All @@ -327,6 +329,20 @@ func (l *Logger) extractLevel(line string) (level, msg string) {
return "INFO", line
}

func (l *Logger) levelMapper(level string) mapFunc {
switch level {
case "TRACE", "DEBUG":
return l.mapper.DebugFunc
case "INFO ":
return l.mapper.InfoFunc
case "WARN ":
return l.mapper.WarnFunc
case "ERROR", "PANIC", "FATAL":
return l.mapper.ErrorFunc
}
return func(s string) string { return s }
}

// getDump reads runtime stack and returns as a string
func getDump() []byte {
maxSize := 5 * 1024 * 1024
Expand Down
70 changes: 70 additions & 0 deletions logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func TestLoggerWithCallerDepth(t *testing.T) {
"func2) something 123 err\n", rout.String())
}

//nolint dupl
func TestLogger_formatWithOptions(t *testing.T) {
tbl := []struct {
opts []Option
Expand Down Expand Up @@ -182,6 +183,75 @@ func TestLogger_formatWithOptions(t *testing.T) {
}
}

//nolint dupl
func TestLogger_formatWithColors(t *testing.T) {
tbl := []struct {
opts []Option
elems layout
res string
}{
{
[]Option{},
layout{DT: time.Date(2018, 1, 7, 13, 2, 34, 0, time.Local), Message: "blah blah", Level: "INFO "},
"!TM=2018/01/07 13:02:34=TM! !IF=INFO =IF! !IF=blah blah=IF!",
},
{
[]Option{Msec},
layout{DT: time.Date(2018, 1, 7, 13, 2, 34, 0, time.Local), Message: "blah blah", Level: "DEBUG"},
"!TM=2018/01/07 13:02:34.000=TM! !DG=DEBUG=DG! !DG=blah blah=DG!",
},
{
[]Option{Msec, LevelBraces},
layout{DT: time.Date(2018, 1, 7, 13, 2, 34, 0, time.Local), Message: "blah blah", Level: "DEBUG"},
"!TM=2018/01/07 13:02:34.000=TM! !DG=[DEBUG]=DG! !DG=blah blah=DG!",
},
{
[]Option{CallerFile, Msec},
layout{DT: time.Date(2018, 1, 7, 13, 2, 34, 0, time.Local), Message: "blah blah", Level: "DEBUG",
CallerFile: "file1.go", CallerLine: 12},
"!TM=2018/01/07 13:02:34.000=TM! !DG=DEBUG=DG! !CL={file1.go:12}=CL! !DG=blah blah=DG!",
},
{
[]Option{CallerFunc, CallerPkg},
layout{DT: time.Date(2018, 1, 7, 13, 2, 34, 0, time.Local), Message: "blah blah", Level: "DEBUG",
CallerFunc: "func1", CallerPkg: "pkg"},
"!TM=2018/01/07 13:02:34=TM! !DG=DEBUG=DG! !CL={func1 pkg}=CL! !DG=blah blah=DG!",
},
}

mp := Mapper{
ErrorFunc: func(s string) string {
return "!ER=" + s + "=ER!"
},
WarnFunc: func(s string) string {
return "!WR=" + s + "=WR!"
},
InfoFunc: func(s string) string {
return "!IF=" + s + "=IF!"
},
DebugFunc: func(s string) string {
return "!DG=" + s + "=DG!"
},
CallerFunc: func(s string) string {
return "!CL=" + s + "=CL!"
},
TimeFunc: func(s string) string {
return "!TM=" + s + "=TM!"
},
}

for n, tt := range tbl {
tt := tt
opts := []Option{}
opts = append(opts, tt.opts...)
opts = append(opts, Map(mp))
l := New(opts...)
t.Run(strconv.Itoa(n), func(t *testing.T) {
assert.Equal(t, tt.res, l.formatWithOptions(tt.elems))
})
}
}

func TestLoggerWithPanic(t *testing.T) {
fatalCalls := 0
rout, rerr := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
Expand Down
26 changes: 26 additions & 0 deletions mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lgr

// Mapper defines optional functions to change elements of the logged message for each part, based on levels.
// Only some mapFunc can be defined, by default does nothing. Can be used to alter the output, for example making some
// part of the output colorful.
type Mapper struct {
ErrorFunc mapFunc
WarnFunc mapFunc
InfoFunc mapFunc
DebugFunc mapFunc

CallerFunc mapFunc
TimeFunc mapFunc
}

type mapFunc func(string) string

// nopMapper is a default, doing nothing
var nopMapper = Mapper{
ErrorFunc: func(s string) string { return s },
WarnFunc: func(s string) string { return s },
InfoFunc: func(s string) string { return s },
DebugFunc: func(s string) string { return s },
CallerFunc: func(s string) string { return s },
TimeFunc: func(s string) string { return s },
}
7 changes: 7 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,10 @@ func Secret(vals ...string) Option {
}
}
}

// Map sets mapper functions to change elements of the logged message based on levels.
func Map(m Mapper) Option {
return func(l *Logger) {
l.mapper = m
}
}

0 comments on commit 9a141d1

Please sign in to comment.