Skip to content

Commit 53ea1cc

Browse files
committed
Added formatters. Fixed an issue where logrus would not set the proper level
1 parent 9820345 commit 53ea1cc

18 files changed

+833
-30
lines changed

.idea/dictionaries/jcdiaz.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/go-logger-lib.iml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/workspace.xml

Lines changed: 483 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

log/entry.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package log
2+
3+
import (
4+
"time"
5+
)
6+
7+
// Entry represents a log entry.
8+
type Entry struct {
9+
// Contains all the fields set by the user. TODO
10+
Data map[string]interface{}
11+
12+
// Time at which the log entry was created
13+
Timestamp time.Time
14+
15+
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
16+
Level Level
17+
18+
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
19+
Message string
20+
21+
metadata map[string]interface{}
22+
}
23+
24+
func (e *Entry) AddMetadata(key string, val interface{}) {
25+
if e.metadata == nil {
26+
e.metadata = map[string]interface{}{}
27+
}
28+
e.metadata[key] = val
29+
}

log/formatter.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package log
2+
3+
import (
4+
"io"
5+
"os"
6+
"strings"
7+
"text/template"
8+
)
9+
10+
const (
11+
TemplateDefault = `{{ .Level }} {{ .Timestamp }} | {{ .Message }}`
12+
)
13+
14+
// The Formatter interface is used to implement a custom Formatter. It takes an
15+
// `Entry`. It exposes all the fields, including the default ones:
16+
//
17+
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
18+
// * `entry.Data["time"]`. The timestamp.
19+
// * `entry.Data["level"]. The level the entry was logged at.
20+
//
21+
// Any additional fields added with `WithField` or `WithFields` are also in
22+
// `entry.Data`. Format is expected to return an array of bytes which are then
23+
// logged to `logger.Out`.
24+
type IFormatter interface {
25+
Format(io.Writer, *Entry) error
26+
SetTemplate(string) error
27+
}
28+
29+
// BaseFormatter base structure for formatters
30+
type BaseFormatter struct {
31+
templateHandler *template.Template
32+
helpers template.FuncMap
33+
}
34+
35+
func (f *BaseFormatter) SetTemplate(tmpl string) error {
36+
t, err := template.New("formatter").Funcs(f.helpers).Parse(tmpl)
37+
if err != nil {
38+
return err
39+
}
40+
f.templateHandler = t
41+
return nil
42+
}
43+
44+
// BaseTerminalFormatter base structure to create formatters for a terminal
45+
type BaseTerminalFormatter struct {
46+
BaseFormatter
47+
// Set to true to bypass checking for a TTY before outputting colors.
48+
ForceColors bool
49+
50+
// Force disabling colors.
51+
DisableColors bool
52+
53+
// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
54+
EnvironmentOverrideColors bool
55+
supportsColor *bool
56+
colorScheme LevelColorScheme
57+
}
58+
59+
func (f *BaseTerminalFormatter) isColored() bool {
60+
if f.supportsColor == nil {
61+
supportsColor := f.ForceColors
62+
63+
if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
64+
supportsColor = true
65+
} else if ok && force == "0" {
66+
supportsColor = false
67+
} else if os.Getenv("CLICOLOR") == "0" {
68+
supportsColor = false
69+
} else if strings.Contains(os.Getenv("TERM"), "color") {
70+
supportsColor = true
71+
}
72+
f.supportsColor = &supportsColor
73+
}
74+
75+
return *f.supportsColor && !f.DisableColors
76+
}
77+
78+
func (f *BaseTerminalFormatter) SetColorScheme(scheme LevelColorScheme) {
79+
f.colorScheme = scheme
80+
}

log/formatter_helpers.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package log
2+
3+
import (
4+
"fmt"
5+
"github.com/jucardi/go-iso8601"
6+
"github.com/jucardi/go-streams/streams"
7+
"github.com/jucardi/go-terminal-colors"
8+
"strings"
9+
"text/template"
10+
)
11+
12+
const (
13+
metadataColorEnabled = "colored"
14+
metadataColorScheme = "color_scheme"
15+
)
16+
17+
func getDefaultHelpers() template.FuncMap {
18+
return template.FuncMap{
19+
"string": stringFn,
20+
"upper": uppercaseFn,
21+
"lower": lowercaseFn,
22+
"fmt": formatFn,
23+
"level": coloredLevelFn,
24+
"timestamp": timestampFn,
25+
"colorCode": colorCodeFn,
26+
"color": colorFn,
27+
}
28+
}
29+
30+
func stringFn(arg interface{}) string {
31+
return fmt.Sprintf("%+v", arg)
32+
}
33+
34+
func uppercaseFn(arg string) string {
35+
return strings.ToUpper(stringFn(arg))
36+
}
37+
38+
func lowercaseFn(arg interface{}) string {
39+
return strings.ToLower(stringFn(arg))
40+
}
41+
42+
func formatFn(format string, args ...interface{}) string {
43+
return fmt.Sprintf(format, args...)
44+
}
45+
46+
func coloredLevelFn(entry Entry) string {
47+
if v, ok := entry.metadata[metadataColorEnabled]; ok && !v.(bool) {
48+
return entry.Level.String()
49+
}
50+
if _, ok := entry.metadata[metadataColorScheme]; !ok {
51+
return entry.Level.String()
52+
}
53+
54+
scheme := entry.metadata[metadataColorScheme].(LevelColorScheme)
55+
colors := scheme[entry.Level]
56+
57+
return fmtc.New().Print(fmt.Sprintf(" %s ", strings.ToUpper(entry.Level.String())), colors...).String()
58+
}
59+
60+
func timestampFn(entry Entry, format string) string {
61+
return iso8601.TimeToString(entry.Timestamp, format)
62+
}
63+
64+
func colorCodeFn(arg interface{}, colors ...fmtc.Color) string {
65+
return fmtc.New().Print(arg, colors...).String()
66+
}
67+
68+
func colorFn(arg interface{}, colors ...string) string {
69+
return fmtc.New().Print(arg, streams.From(colors).Map(func(i interface{}) interface{} {
70+
ret, _ := fmtc.Parse(i.(string))
71+
return ret
72+
}).ToArray().([]fmtc.Color)...).String()
73+
}

log/formatter_terminal.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package log
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"github.com/jucardi/go-terminal-colors"
7+
"io"
8+
)
9+
10+
// TextFormatter formats logs into text
11+
type TerminalFormatter struct {
12+
BaseTerminalFormatter
13+
}
14+
15+
type LevelColorScheme map[Level][]fmtc.Color
16+
17+
const (
18+
TemplateTerminalFormatter = `{{ level . }} {{ color (timestamp . "HH:mm:ss") "Cyan" }} {{ .Message }}`
19+
)
20+
21+
var DefaultColorScheme = LevelColorScheme{
22+
DebugLevel: []fmtc.Color{fmtc.Bold, fmtc.DarkGray},
23+
InfoLevel: []fmtc.Color{fmtc.Bold, fmtc.White, fmtc.BgBlue},
24+
WarnLevel: []fmtc.Color{fmtc.Black, fmtc.BgYellow},
25+
ErrorLevel: []fmtc.Color{fmtc.Bold, fmtc.White, fmtc.BgRed},
26+
FatalLevel: []fmtc.Color{fmtc.Bold, fmtc.White, fmtc.BgRed},
27+
PanicLevel: []fmtc.Color{fmtc.Bold, fmtc.White, fmtc.BgRed},
28+
}
29+
30+
func NewTerminalFormatter() IFormatter {
31+
ret := &TerminalFormatter{}
32+
ret.helpers = getDefaultHelpers()
33+
ret.SetTemplate(TemplateTerminalFormatter)
34+
ret.SetColorScheme(DefaultColorScheme)
35+
return ret
36+
}
37+
38+
// Format renders a single log entry
39+
func (f *TerminalFormatter) Format(writer io.Writer, entry *Entry) error {
40+
if f.templateHandler == nil {
41+
return errors.New("no template parser found")
42+
}
43+
44+
if writer == nil {
45+
return errors.New("writer cannot be nil")
46+
}
47+
48+
entry.AddMetadata(metadataColorEnabled, f.isColored())
49+
entry.AddMetadata(metadataColorScheme, f.colorScheme)
50+
if err := f.templateHandler.Execute(writer, entry); err != nil {
51+
return fmt.Errorf("unable to write log to io writer, %s", err.Error())
52+
}
53+
54+
fmt.Fprintln(writer)
55+
return nil
56+
}

0 commit comments

Comments
 (0)