commit 45decbb7786891f98ae7cf5feea2b454b374eaf5 Author: jolheiser Date: Tue Jan 28 22:01:33 2020 -0600 Initial commit Signed-off-by: jolheiser diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a453954 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# GoLand +.idea/ \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..6d71439 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,23 @@ +linters: + enable: + - deadcode + - dogsled + - dupl + - errcheck + - funlen + - gocognit + - goconst + - gocritic + - gocyclo + - gofmt + - golint + - gosimple + - govet + - misspell + - prealloc + - staticcheck + - structcheck + - typecheck + - unparam + - unused + - varcheck \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b277945 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +GO ?= go + +.PHONY: lint +lint: + @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + export BINARY="golangci-lint"; \ + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.1; \ + fi + golangci-lint run --timeout 5m + +.PHONY: fmt +fmt: + $(GO) fmt ./... \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d495e3 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# beaver + +Make short work of your logs + +## Loggers +Beaver comes ready to log to `stdout` via shorthand `beaver.Info` etc. usage. +However, Beaver can also create new standalone loggers with `beaver.New`. +Beaver loggers can write to anything implementing the `io.Writer` interface. + +## Options +| Option | Type | Effect | +|:------------:|:-------:|:-------------------------------------------------------:| +| TimePrefix | boolean | Prepends the date/time | +| StackPrefix | boolean | Prepends the calling file/line | +| StackLimit | integer | Amount of calling file to show, defaults to entire path | +| LevelPrefix | boolean | Prepends a logging level prefix, e.g. [T] for Trace | +| LevelColor | boolean | Colors the LevelPrefix if enabled | +| MessageColor | boolean | Colors the message itself | + +The default Console configuration is below +Colored messages convey the logging level while reducing the amount of space used for CLI applications + +| Option | Value | +|:------------:|:-----:| +| TimePrefix | false | +| StackPrefix | false | +| StackLimit | 0 | +| LevelPrefix | false | +| LevelColor | false | +| MessageColor | true | + + +## Colors +Beaver allows you to customize the colors of various parts of the message + +| `color.` | Default Format | +|:--------:|----------------| +| Trace | Bold, FgCyan | +| Debug | Bold, FgBlue | +| Info | Bold, FgGreen | +| Warn | Bold, FgYellow | +| Error | Bold, FgRed | +| Fatal | Bold, BgRed | +| Default | None | +| Time | `Default` | +| Stack | `Default` | + +```go +// Set Trace logging to Magenta instead of Bold-Cyan +color.Trace = color.New(color.FgMagenta) +``` \ No newline at end of file diff --git a/_examples/examples.go b/_examples/examples.go new file mode 100644 index 0000000..22f9a6d --- /dev/null +++ b/_examples/examples.go @@ -0,0 +1,107 @@ +package main + +import ( + "fmt" + "os" + + "gitea.com/jolheiser/beaver" + "gitea.com/jolheiser/beaver/color" +) + +func main() { + + // Formatting + fmt.Println(color.New(color.Reset).Format("Reset")) + fmt.Println(color.New(color.Bold).Format("Bold")) + fmt.Println(color.New(color.Faint).Format("Faint")) + fmt.Println(color.New(color.Italic).Format("Italic")) + fmt.Println(color.New(color.Underline).Format("Underline")) + fmt.Println(color.New(color.BlinkSlow).Format("BlinkSlow")) + fmt.Println(color.New(color.BlinkRapid).Format("BlinkRapid")) + fmt.Println(color.New(color.ReverseVideo).Format("ReverseVideo")) + fmt.Println(color.New(color.Concealed).Format("Concealed")) + fmt.Println(color.New(color.CrossedOut).Format("CrossedOut")) + + // Foreground + fmt.Println(color.New(color.FgBlack).Format("FgBlack")) + fmt.Println(color.New(color.FgRed).Format("FgRed")) + fmt.Println(color.New(color.FgGreen).Format("FgGreen")) + fmt.Println(color.New(color.FgYellow).Format("FgYellow")) + fmt.Println(color.New(color.FgBlue).Format("FgBlue")) + fmt.Println(color.New(color.FgMagenta).Format("FgMagenta")) + fmt.Println(color.New(color.FgCyan).Format("FgCyan")) + fmt.Println(color.New(color.FgWhite).Format("FgWhite")) + // Hi + fmt.Println(color.New(color.FgHiBlack).Format("FgHiBlack")) + fmt.Println(color.New(color.FgHiRed).Format("FgHiRed")) + fmt.Println(color.New(color.FgHiGreen).Format("FgHiGreen")) + fmt.Println(color.New(color.FgHiYellow).Format("FgHiYellow")) + fmt.Println(color.New(color.FgHiBlue).Format("FgHiBlue")) + fmt.Println(color.New(color.FgHiMagenta).Format("FgHiMagenta")) + fmt.Println(color.New(color.FgHiCyan).Format("FgHiCyan")) + fmt.Println(color.New(color.FgHiWhite).Format("FgHiWhite")) + + // Background + fmt.Println(color.New(color.BgBlack).Format("BgBlack")) + fmt.Println(color.New(color.BgRed).Format("BgRed")) + fmt.Println(color.New(color.BgGreen).Format("BgGreen")) + fmt.Println(color.New(color.BgYellow).Format("BgYellow")) + fmt.Println(color.New(color.BgBlue).Format("BgBlue")) + fmt.Println(color.New(color.BgMagenta).Format("BgMagenta")) + fmt.Println(color.New(color.BgCyan).Format("BgCyan")) + fmt.Println(color.New(color.BgWhite).Format("BgWhite")) + // Hi + fmt.Println(color.New(color.BgHiBlack).Format("BgHiBlack")) + fmt.Println(color.New(color.BgHiRed).Format("BgHiRed")) + fmt.Println(color.New(color.BgHiGreen).Format("BgHiGreen")) + fmt.Println(color.New(color.BgHiYellow).Format("BgHiYellow")) + fmt.Println(color.New(color.BgHiBlue).Format("BgHiBlue")) + fmt.Println(color.New(color.BgHiMagenta).Format("BgHiMagenta")) + fmt.Println(color.New(color.BgHiCyan).Format("BgHiCyan")) + fmt.Println(color.New(color.BgHiWhite).Format("BgHiWhite")) + + // Presets + beaver.Console.Level = beaver.TRACE + beaver.Console.Format.LevelPrefix = true + beaver.Console.Format.LevelColor = true + beaver.Log(beaver.TRACE, "Trace") + beaver.Log(beaver.DEBUG, "Debug") + beaver.Log(beaver.INFO, "Info") + beaver.Log(beaver.WARN, "Warn") + beaver.Log(beaver.ERROR, "Error") + beaver.Log(beaver.FATAL, "Fatal") + + // Time Prefix + b := beaver.New(os.Stdout, beaver.INFO, beaver.FormatOptions{ + TimePrefix: true, + }) + b.Info("\t\tTime") + + // Stack Prefix + b.Format = beaver.FormatOptions{ + StackPrefix: true, + StackLimit: 25, + } + b.Info("Stack") + + // All options, non-color + b.Format = beaver.FormatOptions{ + TimePrefix: true, + StackPrefix: true, + LevelPrefix: true, + } + b.Info("Full options with no color") + + // All options, Gitea colors + b.Format = beaver.FormatOptions{ + TimePrefix: true, + StackPrefix: true, + StackLimit: 20, + LevelPrefix: true, + LevelColor: true, + MessageColor: false, + } + color.Time = color.New(color.FgCyan) + color.Stack = color.New(color.FgGreen) + b.Info("Full options emulating Gitea") +} diff --git a/color/color.go b/color/color.go new file mode 100644 index 0000000..27b06ea --- /dev/null +++ b/color/color.go @@ -0,0 +1,168 @@ +package color + +import ( + "fmt" + "strconv" + "strings" +) + +// Color defines a custom color object which is defined by SGR parameters. +type Color struct { + Attrs []Attribute +} + +// Attribute defines a single SGR Code +type Attribute int + +const escape = "\x1b" + +// Logger attributes +const ( + Reset Attribute = iota + Bold + Faint + Italic + Underline + BlinkSlow + BlinkRapid + ReverseVideo + Concealed + CrossedOut +) + +// Foreground text colors +const ( + FgBlack Attribute = iota + 30 + FgRed + FgGreen + FgYellow + FgBlue + FgMagenta + FgCyan + FgWhite +) + +// Foreground Hi-Intensity text colors +const ( + FgHiBlack Attribute = iota + 90 + FgHiRed + FgHiGreen + FgHiYellow + FgHiBlue + FgHiMagenta + FgHiCyan + FgHiWhite +) + +// Background text colors +const ( + BgBlack Attribute = iota + 40 + BgRed + BgGreen + BgYellow + BgBlue + BgMagenta + BgCyan + BgWhite +) + +// Background Hi-Intensity text colors +const ( + BgHiBlack Attribute = iota + 100 + BgHiRed + BgHiGreen + BgHiYellow + BgHiBlue + BgHiMagenta + BgHiCyan + BgHiWhite +) + +// New returns a new Color with attrs Attributes +func New(attrs ...Attribute) *Color { + return &Color{Attrs: attrs} +} + +// Add adds new Attributes to a Color +func (c *Color) Add(attrs ...Attribute) { + for _, attr := range attrs { + has := false + for _, att := range c.Attrs { + if attr == att { + has = true + break + } + } + if !has { + c.Attrs = append(c.Attrs, attr) + } + } +} + +// String returns a string representation of the sum of a Color's Attributes +func (c *Color) String() string { + attrs := 0 + for _, attr := range c.Attrs { + attrs += int(attr) + } + return fmt.Sprintf("%d", attrs) +} + +func (c *Color) sequence() string { + format := make([]string, len(c.Attrs)) + for i, v := range c.Attrs { + format[i] = strconv.Itoa(int(v)) + } + + return strings.Join(format, ";") +} + +func (c *Color) wrap(s string) string { + return c.format() + s + c.unformat() +} + +func (c *Color) format() string { + return fmt.Sprintf("%s[%sm", escape, c.sequence()) +} + +func (c *Color) unformat() string { + return fmt.Sprintf("%s[%dm", escape, Reset) +} + +func (c *Color) Format(text string) string { + if len(c.Attrs) > 0 { + return c.wrap(text) + } + return text +} + +// ParseLevel parses a string and returns a Beaver Level's Color, defaulting to Info +func ParseLevel(level string) *Color { + switch strings.ToUpper(level) { + case "T", "TRACE": + return Trace + case "D", "DEBUG": + return Debug + case "I", "INFO": + return Info + case "W", "WARN": + return Warn + case "E", "ERROR": + return Error + case "F", "FATAL": + return Fatal + } + return Info +} + +var ( + Trace = New(Bold, FgCyan) + Debug = New(Bold, FgBlue) + Info = New(Bold, FgGreen) + Warn = New(Bold, FgYellow) + Error = New(Bold, FgRed) + Fatal = New(Bold, BgRed) + Default = New() + Time = Default + Stack = Default +) diff --git a/color/color_test.go b/color/color_test.go new file mode 100644 index 0000000..8a03220 --- /dev/null +++ b/color/color_test.go @@ -0,0 +1,77 @@ +package color + +import ( + "os" + "testing" +) + +var color = New() + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func TestColor(t *testing.T) { + tt := []struct { + Name string + SetAttrs []Attribute + AddAttrs []Attribute + Size int + }{ + {"Init", nil, nil, 0}, + {"Set 2", []Attribute{Bold, FgBlack}, nil, 2}, + {"Add 2", nil, []Attribute{Italic, BgWhite}, 4}, + {"Set 3", []Attribute{Bold, FgBlack, BgWhite}, nil, 3}, + {"Add 3", nil, []Attribute{Italic, FgWhite, BgBlack}, 6}, + {"Add Same", nil, []Attribute{Italic, FgWhite, BgBlack}, 6}, + {"Add 2 Same", nil, []Attribute{Italic, FgWhite, BgGreen}, 7}, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + if tc.SetAttrs != nil { + color.Attrs = tc.SetAttrs + } + if tc.AddAttrs != nil { + color.Add(tc.AddAttrs...) + } + if len(color.Attrs) != tc.Size { + t.Logf("color has `%d` attributes, but should have `%d`", len(color.Attrs), tc.Size) + } + }) + } +} + +func TestParseLevel(t *testing.T) { + tt := []struct { + Parse string + Expected *Color + }{ + {"T", Trace}, + {"Trace", Trace}, + {"D", Debug}, + {"Debug", Debug}, + {"I", Info}, + {"Info", Info}, + {"W", Warn}, + {"Warn", Warn}, + {"E", Error}, + {"Error", Error}, + {"F", Fatal}, + {"Fatal", Fatal}, + {"Unknown", Info}, + {"N/A", Info}, + {"1234", Info}, + {"A Space", Info}, + } + + for _, tc := range tt { + t.Run(tc.Parse, func(t *testing.T) { + level := ParseLevel(tc.Parse) + if level != tc.Expected { + t.Logf("Expected `%s`, got `%s`", tc.Expected, level) + t.Fail() + } + }) + } +} diff --git a/console.go b/console.go new file mode 100644 index 0000000..ccf081e --- /dev/null +++ b/console.go @@ -0,0 +1,88 @@ +package beaver + +import ( + "os" +) + +var ( + Console = &Logger{ + Writer: os.Stdout, + Level: INFO, + Format: FormatOptions{ + MessageColor: true, + }, + skip: 4, + } +) + +// Low-Level Log +func Log(level Level, v ...interface{}) { + Console.Log(level, v...) +} + +// Low-Level formatted Log +func Logf(level Level, format string, v ...interface{}) { + Console.Logf(level, format, v...) +} + +// Trace logs at TRACE Level +func Trace(v ...interface{}) { + Console.Trace(v...) +} + +// Tracef formats logs at TRACE Level +func Tracef(format string, v ...interface{}) { + Console.Tracef(format, v...) +} + +// Debug logs at DEBUG Level +func Debug(v ...interface{}) { + Console.Debug(v...) +} + +// Debugf formats logs at DEBUG Level +func Debugf(format string, v ...interface{}) { + Console.Debugf(format, v...) +} + +// Info logs at INFO Level +func Info(v ...interface{}) { + Console.Info(v...) +} + +// Infof formats logs at INFO Level +func Infof(format string, v ...interface{}) { + Console.Infof(format, v...) +} + +// Warn logs at WARN Level +func Warn(v ...interface{}) { + Console.Warn(v...) +} + +// Warnf formats logs at WARN Level +func Warnf(format string, v ...interface{}) { + Console.Warnf(format, v...) +} + +// Error logs at ERROR Level +func Error(v ...interface{}) { + Console.Error(v...) +} + +// Errorf formats logs at ERROR Level +func Errorf(format string, v ...interface{}) { + Console.Errorf(format, v...) +} + +// Fatal logs at FATAL Level +func Fatal(v ...interface{}) { + Console.Fatal(v...) + os.Exit(1) +} + +// Fatalf formats logs at FATAL Level +func Fatalf(format string, v ...interface{}) { + Console.Fatalf(format, v...) + os.Exit(1) +} diff --git a/console_test.go b/console_test.go new file mode 100644 index 0000000..83e1f24 --- /dev/null +++ b/console_test.go @@ -0,0 +1,39 @@ +package beaver + +import ( + "fmt" + "testing" +) + +func TestConsole(t *testing.T) { + Console.Writer = testBuffer + tt := []struct { + Name string + Level Level + }{ + {Name: "Trace Level", Level: TRACE}, + {Name: "Debug Level", Level: DEBUG}, + {Name: "Info Level", Level: INFO}, + {Name: "Warn Level", Level: WARN}, + {Name: "Error Level", Level: ERROR}, + {Name: "Fatal Level", Level: FATAL}, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + for _, lvl := range Levels() { + Console.Level = tc.Level + + t.Run(fmt.Sprintf("%s Log", lvl), func(t *testing.T) { + Log(lvl, MESSAGE) + check(t, tc.Level, lvl) + }) + + t.Run(fmt.Sprintf("%s Logf", lvl), func(t *testing.T) { + Logf(lvl, "%s", MESSAGE) + check(t, tc.Level, lvl) + }) + } + }) + } +} diff --git a/console_windows.go b/console_windows.go new file mode 100644 index 0000000..c7bd03e --- /dev/null +++ b/console_windows.go @@ -0,0 +1,13 @@ +package beaver + +import "golang.org/x/sys/windows" + +func init() { + mode := uint32(0) + if err := windows.GetConsoleMode(windows.Stdout, &mode); err != nil { + return + } + + mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING + _ = windows.SetConsoleMode(windows.Stdout, mode) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2e62c98 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module gitea.com/jolheiser/beaver + +go 1.12 + +require golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b1bd14d --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/level.go b/level.go new file mode 100644 index 0000000..d8e5950 --- /dev/null +++ b/level.go @@ -0,0 +1,142 @@ +package beaver + +import ( + "fmt" + "strings" + "time" + + "gitea.com/jolheiser/beaver/color" +) + +var bold = color.New(color.Bold) + +// Level defines a Beaver logging level +type Level int + +const ( + TRACE Level = iota + DEBUG + INFO + WARN + ERROR + FATAL +) + +// String returns a human-friendly string +func (l Level) String() string { + switch l { + case TRACE: + return "Trace" + case DEBUG: + return "Debug" + case INFO: + return "Info" + case WARN: + return "Warn" + case ERROR: + return "Error" + case FATAL: + return "Fatal" + default: + return "N/A" + } +} + +// Prefix returns a prefix for a logging level, optionally colored +func (l Level) Prefix() string { + var letter string + switch l { + case TRACE: + letter = "T" + case DEBUG: + letter = "D" + case INFO: + letter = "I" + case WARN: + letter = "W" + case ERROR: + letter = "E" + case FATAL: + letter = "F" + } + return fmt.Sprintf("[%s]", letter) +} + +// Color returns a Level's color, defaulting to bold +func (l Level) Color() *color.Color { + switch l { + case TRACE: + return color.Trace + case DEBUG: + return color.Debug + case INFO: + return color.Info + case WARN: + return color.Warn + case ERROR: + return color.Error + case FATAL: + return color.Fatal + default: + return bold + } +} + +// Levels returns a list of Beaver logging levels +func Levels() []Level { + return []Level{TRACE, DEBUG, INFO, WARN, ERROR, FATAL} +} + +// ParseLevel parses a string and returns a Beaver Level, defaulting to Info +func ParseLevel(level string) Level { + switch strings.ToUpper(level) { + case "T", "TRACE": + return TRACE + case "D", "DEBUG": + return DEBUG + case "I", "INFO": + return INFO + case "W", "WARN": + return WARN + case "E", "ERROR": + return ERROR + case "F", "FATAL": + return FATAL + } + return INFO +} + +func timePrefix(t time.Time) string { + var buf = &[]byte{} + year, month, day := t.Date() + itoa(buf, int(month), 2) + *buf = append(*buf, '/') + itoa(buf, day, 2) + *buf = append(*buf, '/') + itoa(buf, year, 4) + *buf = append(*buf, ' ') + + hour, min, sec := t.Clock() + itoa(buf, hour, 2) + *buf = append(*buf, ':') + itoa(buf, min, 2) + *buf = append(*buf, ':') + itoa(buf, sec, 2) + + return string(*buf) +} + +func itoa(buf *[]byte, i int, wid int) { + var b [20]byte + bp := len(b) - 1 + for i >= 10 || wid > 1 { + wid-- + q := i / 10 + b[bp] = byte('0' + i - q*10) + bp-- + i = q + } + // i < 10 + b[bp] = byte('0' + i) + *buf = append(*buf, b[bp:]...) +} diff --git a/level_test.go b/level_test.go new file mode 100644 index 0000000..f587ac6 --- /dev/null +++ b/level_test.go @@ -0,0 +1,37 @@ +package beaver + +import "testing" + +func TestParseLevel(t *testing.T) { + tt := []struct { + Parse string + Expected Level + }{ + {"T", TRACE}, + {"Trace", TRACE}, + {"D", DEBUG}, + {"Debug", DEBUG}, + {"I", INFO}, + {"Info", INFO}, + {"W", WARN}, + {"Warn", WARN}, + {"E", ERROR}, + {"Error", ERROR}, + {"F", FATAL}, + {"Fatal", FATAL}, + {"Unknown", INFO}, + {"N/A", INFO}, + {"1234", INFO}, + {"A Space", INFO}, + } + + for _, tc := range tt { + t.Run(tc.Parse, func(t *testing.T) { + level := ParseLevel(tc.Parse) + if level != tc.Expected { + t.Logf("Expected `%s`, got `%s`", tc.Expected, level) + t.Fail() + } + }) + } +} diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..c337d62 --- /dev/null +++ b/logger.go @@ -0,0 +1,161 @@ +package beaver + +import ( + "fmt" + "io" + "os" + "runtime" + "strings" + "time" + + "gitea.com/jolheiser/beaver/color" +) + +// Logger is a base Beaver logger +type Logger struct { + Writer io.Writer + Level Level + Format FormatOptions + skip int +} + +// FormatOptions defines formatting options for a logger +type FormatOptions struct { + TimePrefix bool + StackPrefix bool + StackLimit int + LevelPrefix bool + LevelColor bool + MessageColor bool +} + +// New returns a new Beaver logger +func New(writer io.Writer, level Level, opts FormatOptions) *Logger { + return &Logger{ + Writer: writer, + Level: level, + Format: opts, + skip: 3, + } +} + +func (l *Logger) write(level Level, text string) { + if l.Level <= level { + _, file, line, _ := runtime.Caller(l.skip) + var message string + if l.Format.TimePrefix { + message += color.Time.Format(timePrefix(time.Now())) + " " + } + if l.Format.StackPrefix { + idx := len(file) - l.Format.StackLimit + if l.Format.StackLimit <= 0 { + idx = 0 + } + if idx > 0 { + file = fmt.Sprintf("...%s", file[idx:]) + } + message += color.Stack.Format(fmt.Sprintf("%s:%d", file, line)) + " " + } + if l.Format.LevelPrefix { + if l.Format.LevelColor { + message += level.Color().Format(level.Prefix()) + } else { + message += level.Prefix() + } + message += " " + } + if l.Format.MessageColor { + message += level.Color().Format(text) + } else { + message += text + } + message += "\n" + _, _ = l.Writer.Write([]byte(message)) + } +} + +func (l *Logger) log(level Level, v ...interface{}) { + var args = make([]string, len(v)) + for i := 0; i < len(v); i++ { + args[i] = fmt.Sprintf("%v", v[i]) + } + text := strings.Join(args, " ") + l.write(level, text) +} + +func (l *Logger) logf(level Level, format string, v ...interface{}) { + text := fmt.Sprintf(format, v...) + l.write(level, text) +} + +// Low-Level Log +func (l *Logger) Log(level Level, v ...interface{}) { + l.log(level, v...) +} + +// Low-Level formatted Log +func (l *Logger) Logf(level Level, format string, v ...interface{}) { + l.logf(level, format, v...) +} + +// Trace logs at TRACE Level +func (l *Logger) Trace(v ...interface{}) { + l.log(TRACE, v...) +} + +// Tracef formats logs at TRACE Level +func (l *Logger) Tracef(format string, v ...interface{}) { + l.logf(TRACE, format, v...) +} + +// Debug logs at DEBUG Level +func (l *Logger) Debug(v ...interface{}) { + l.log(DEBUG, v...) +} + +// Debugf formats logs at DEBUG Level +func (l *Logger) Debugf(format string, v ...interface{}) { + l.logf(DEBUG, format, v...) +} + +// Info logs at INFO Level +func (l *Logger) Info(v ...interface{}) { + l.log(INFO, v...) +} + +// Infof formats logs at INFO Level +func (l *Logger) Infof(format string, v ...interface{}) { + l.logf(INFO, format, v...) +} + +// Warn logs at WARN Level +func (l *Logger) Warn(v ...interface{}) { + l.log(WARN, v...) +} + +// Warnf formats logs at WARN Level +func (l *Logger) Warnf(format string, v ...interface{}) { + l.logf(WARN, format, v...) +} + +// Error logs at ERROR Level +func (l *Logger) Error(v ...interface{}) { + l.log(ERROR, v...) +} + +// Errorf formats logs at ERROR Level +func (l *Logger) Errorf(format string, v ...interface{}) { + l.logf(ERROR, format, v...) +} + +// Fatal logs at FATAL Level +func (l *Logger) Fatal(v ...interface{}) { + l.log(FATAL, v...) + os.Exit(1) +} + +// Fatalf formats logs at FATAL Level +func (l *Logger) Fatalf(format string, v ...interface{}) { + l.logf(FATAL, format, v...) + os.Exit(1) +} diff --git a/logger_test.go b/logger_test.go new file mode 100644 index 0000000..0cd34e5 --- /dev/null +++ b/logger_test.go @@ -0,0 +1,71 @@ +package beaver + +import ( + "bytes" + "fmt" + "os" + "strings" + "testing" +) + +var testBuffer = &bytes.Buffer{} + +const MESSAGE = "Test" + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func TestLogger(t *testing.T) { + log := New(testBuffer, TRACE, FormatOptions{ + MessageColor: true, + }) + tt := []struct { + Name string + Level Level + }{ + {Name: "Trace Level", Level: TRACE}, + {Name: "Debug Level", Level: DEBUG}, + {Name: "Info Level", Level: INFO}, + {Name: "Warn Level", Level: WARN}, + {Name: "Error Level", Level: ERROR}, + {Name: "Fatal Level", Level: FATAL}, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + for _, lvl := range Levels() { + log.Level = tc.Level + + t.Run(fmt.Sprintf("%s Log", lvl), func(t *testing.T) { + log.Log(lvl, MESSAGE) + check(t, tc.Level, lvl) + }) + + t.Run(fmt.Sprintf("%s Logf", lvl), func(t *testing.T) { + log.Logf(lvl, "%s", MESSAGE) + check(t, tc.Level, lvl) + }) + } + }) + } +} + +func check(t *testing.T, current, test Level) { + f := flush() + m := test.Color().Format(MESSAGE) + if current <= test && f != m { + t.Logf("Expected `%s`, got `%s`", m, f) + t.Fail() + } + if current > test && f == m { + t.Logf("Expected no logging, got `%s`", f) + t.Fail() + } +} + +func flush() string { + msg := strings.TrimSpace(testBuffer.String()) + testBuffer.Reset() + return msg +}