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