commit
45decbb778
|
@ -0,0 +1,2 @@
|
||||||
|
# GoLand
|
||||||
|
.idea/
|
|
@ -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
|
|
@ -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 ./...
|
|
@ -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)
|
||||||
|
```
|
|
@ -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")
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
|
@ -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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module gitea.com/jolheiser/beaver
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
|
|
@ -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=
|
|
@ -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:]...)
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
Reference in New Issue