Initial Commit

This commit is contained in:
Elara 2022-06-30 03:07:29 -07:00
commit 0fbc5dd1d9
12 changed files with 1691 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Arsen Musayelyan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

34
README.md Normal file
View File

@ -0,0 +1,34 @@
# Logger
Logger is a very simple and fast logger that can write both machine-readable and human-readable logs.
### Why?
I made logger because I really liked zerolog, and especially its `ConsoleLogger`, as it looks really nice in the terminal. I use it in all my command-line applications, but there's an issue. Zerolog can only output in JSON or CBOR, depending on buid tags. It cannot produce human-readable output. Therefore, zerolog has to use reflection to unmarshal JSON generated by the logger and then make human-readable output out of it. This is, in my opinion, incredibly and unnecessarily wasteful, so I made logger. Logger can use any logging implementation that implements the `logger.Logger` interface. I currently have four implementations: `JSONLogger`, `PrettyLogger`, `MultiLogger`, and `NopLogger`. The names should be self-explanatory.
### Who is logger for?
If you need a fast and simple logger, but don't need more advanced features such as context and hooks, logger is for you. It cuts out unnecessary features, providing a very simple codebase that is easy to test and has much less potential for bugs, and it's faster than zerolog while also allowing more flexibility and a similar API.
### Who is logger not for?
If you need more advanced features, such as context and hooks, use either zerolog or the even faster zap library. Logger keeps it as simple as possible, only providing logging and nothing else.
### Benchmarks
Logger is very fast. Here are its benchmarks, done on my laptop:
```text
goos: linux
goarch: amd64
pkg: logger
cpu: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
BenchmarkJSON/one-field-8 4216245 294.0 ns/op 160 B/op 3 allocs/op
BenchmarkJSON/two-field-8 1939634 594.3 ns/op 188 B/op 5 allocs/op
BenchmarkJSON/all-8 310526 3955 ns/op 752 B/op 21 allocs/op
BenchmarkPretty/one-field-8 1603789 658.5 ns/op 168 B/op 4 allocs/op
BenchmarkPretty/two-field-8 1388920 864.5 ns/op 200 B/op 6 allocs/op
BenchmarkPretty/all-8 285554 3726 ns/op 760 B/op 22 allocs/op
```
To run the benchmarks yourself, simply clone this repo and run `go test -bench=.`. Keep in mind that they will be different, depending on what your computer's specs are.

13
go.mod Normal file
View File

@ -0,0 +1,13 @@
module go.arsenm.dev/logger
go 1.18
require (
github.com/gookit/color v1.5.1
github.com/mattn/go-isatty v0.0.14
)
require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
)

19
go.sum Normal file
View File

@ -0,0 +1,19 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

280
json.go Normal file
View File

@ -0,0 +1,280 @@
package logger
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"time"
)
var _ Logger = (*JSONLogger)(nil)
// JSONLogger implements the Logger interface
// using JSON for log messages.
type JSONLogger struct {
Out io.Writer
Level LogLevel
noPanic bool
noExit bool
}
// NewJSON creates and returns a new JSONLogger
// If the input writer is io.Discard, NopLogger
// will be returned.
func NewJSON(out io.Writer) *JSONLogger {
return &JSONLogger{Out: out, Level: LogLevelInfo}
}
// NoPanic prevents the logger from panicking on panic events
func (jl *JSONLogger) NoPanic() {
jl.noPanic = true
}
// NoExit prevents the logger from exiting on fatal events
func (jl *JSONLogger) NoExit() {
jl.noExit = true
}
// Debug creates a new debug event with the given message
func (jl *JSONLogger) Debug(msg string) LogBuilder {
return newJSONLogBuilder(jl, msg, LogLevelDebug)
}
// Debugf creates a new debug event with the formatted message
func (jl *JSONLogger) Debugf(format string, v ...any) LogBuilder {
return newJSONLogBuilder(jl, fmt.Sprintf(format, v...), LogLevelDebug)
}
// Info creates a new info event with the given message
func (jl *JSONLogger) Info(msg string) LogBuilder {
return newJSONLogBuilder(jl, msg, LogLevelInfo)
}
// Infof creates a new info event with the formatted message
func (jl *JSONLogger) Infof(format string, v ...any) LogBuilder {
return newJSONLogBuilder(jl, fmt.Sprintf(format, v...), LogLevelInfo)
}
// Warn creates a new warn event with the given message
func (jl *JSONLogger) Warn(msg string) LogBuilder {
return newJSONLogBuilder(jl, msg, LogLevelWarn)
}
// Warnf creates a new warn event with the formatted message
func (jl *JSONLogger) Warnf(format string, v ...any) LogBuilder {
return newJSONLogBuilder(jl, fmt.Sprintf(format, v...), LogLevelWarn)
}
// Error creates a new error event with the given message
func (jl *JSONLogger) Error(msg string) LogBuilder {
return newJSONLogBuilder(jl, msg, LogLevelError)
}
// Errorf creates a new error event with the formatted message
func (jl *JSONLogger) Errorf(format string, v ...any) LogBuilder {
return newJSONLogBuilder(jl, fmt.Sprintf(format, v...), LogLevelError)
}
// Fatal creates a new fatal event with the given message
//
// When sent, fatal events will cause a call to os.Exit(1)
func (jl *JSONLogger) Fatal(msg string) LogBuilder {
return newJSONLogBuilder(jl, msg, LogLevelFatal)
}
// Fatalf creates a new fatal event with the formatted message
//
// When sent, fatal events will cause a call to os.Exit(1)
func (jl *JSONLogger) Fatalf(format string, v ...any) LogBuilder {
return newJSONLogBuilder(jl, fmt.Sprintf(format, v...), LogLevelFatal)
}
// Panic creates a new panic event with the given message
//
// When sent, panic events will cause a panic
func (jl *JSONLogger) Panic(msg string) LogBuilder {
return newJSONLogBuilder(jl, msg, LogLevelPanic)
}
// Panicf creates a new panic event with the formatted message
//
// When sent, panic events will cause a panic
func (jl *JSONLogger) Panicf(format string, v ...any) LogBuilder {
return newJSONLogBuilder(jl, fmt.Sprintf(format, v...), LogLevelPanic)
}
// JSONLogBuilder implements the LogBuilder interface
// using JSON for log messages
type JSONLogBuilder struct {
l *JSONLogger
lvl LogLevel
out writer
}
func newJSONLogBuilder(jl *JSONLogger, msg string, lvl LogLevel) LogBuilder {
if jl.Out == io.Discard || lvl < jl.Level {
return NopLogBuilder{}
}
lb := &JSONLogBuilder{
out: writer{&bytes.Buffer{}, jl.Out},
lvl: lvl,
l: jl,
}
lb.out.WriteString(`{"msg":"`)
lb.out.WriteString(msg)
lb.out.WriteString(`","level":"`)
lb.out.WriteString(logLevelNames[lvl])
lb.out.WriteByte('"')
return lb
}
// writeKey writes a JSON key to the buffer
func (jlb *JSONLogBuilder) writeKey(k string) {
jlb.out.WriteString(`,"`)
jlb.out.WriteString(k)
jlb.out.WriteString(`":`)
}
// Int adds an int field to the output
func (jlb *JSONLogBuilder) Int(key string, val int) LogBuilder {
return jlb.Int64(key, int64(val))
}
// Int64 adds an int64 field to the output
func (jlb *JSONLogBuilder) Int64(key string, val int64) LogBuilder {
jlb.writeKey(key)
jlb.out.WriteString(strconv.FormatInt(val, 10))
return jlb
}
// Int32 adds an int32 field to the output
func (jlb *JSONLogBuilder) Int32(key string, val int32) LogBuilder {
return jlb.Int64(key, int64(val))
}
// Int16 adds an int16 field to the output
func (jlb *JSONLogBuilder) Int16(key string, val int16) LogBuilder {
return jlb.Int64(key, int64(val))
}
// Int8 adds an int8 field to the output
func (jlb *JSONLogBuilder) Int8(key string, val int8) LogBuilder {
return jlb.Int64(key, int64(val))
}
// Uint adds a uint field to the output
func (jlb *JSONLogBuilder) Uint(key string, val uint) LogBuilder {
return jlb.Uint64(key, uint64(val))
}
// Uint64 adds a uint64 field to the output
func (jlb *JSONLogBuilder) Uint64(key string, val uint64) LogBuilder {
jlb.writeKey(key)
jlb.out.WriteString(strconv.FormatUint(val, 10))
return jlb
}
// Uint32 adds a uint32 field to the output
func (jlb *JSONLogBuilder) Uint32(key string, val uint32) LogBuilder {
return jlb.Uint64(key, uint64(val))
}
// Uint16 adds a uint16 field to the output
func (jlb *JSONLogBuilder) Uint16(key string, val uint16) LogBuilder {
return jlb.Uint64(key, uint64(val))
}
// Uint8 adds a uint8 field to the output
func (jlb *JSONLogBuilder) Uint8(key string, val uint8) LogBuilder {
return jlb.Uint64(key, uint64(val))
}
// float adds a float of specified bitsize to the output
func (jlb *JSONLogBuilder) float(key string, val float64, bitsize int) LogBuilder {
jlb.writeKey(key)
jlb.out.WriteString(strconv.FormatFloat(val, 'f', -1, bitsize))
return jlb
}
// Float64 adds a float64 field to the output
func (jlb *JSONLogBuilder) Float64(key string, val float64) LogBuilder {
return jlb.float(key, val, 64)
}
// Float32 adds a float32 field to the output
func (jlb *JSONLogBuilder) Float32(key string, val float32) LogBuilder {
return jlb.float(key, float64(val), 32)
}
// Stringer calls the String method of an fmt.Stringer
// and adds the resulting string as a field to the output
func (jlb *JSONLogBuilder) Stringer(key string, s fmt.Stringer) LogBuilder {
return jlb.Str(key, s.String())
}
// Bytes writes base64-encoded bytes as a field to the output
func (jlb *JSONLogBuilder) Bytes(key string, b []byte) LogBuilder {
return jlb.Str(key, base64.StdEncoding.EncodeToString(b))
}
// Timestamp adds the time formatted as RFC3339Nano
// as a field to the output using the key "timestamp"
func (jlb *JSONLogBuilder) Timestamp() LogBuilder {
return jlb.Str("timestamp", time.Now().Format(time.RFC3339Nano))
}
// Bool adds a bool as a field to the output
func (jlb *JSONLogBuilder) Bool(key string, val bool) LogBuilder {
jlb.writeKey(key)
if val {
jlb.out.WriteString("true")
} else {
jlb.out.WriteString("false")
}
return jlb
}
// Str adds a string as a field to the output
func (jlb *JSONLogBuilder) Str(key, val string) LogBuilder {
jlb.writeKey(key)
jlb.out.WriteByte('"')
jlb.out.WriteString(val)
jlb.out.WriteByte('"')
return jlb
}
// Any uses reflection to marshal any type and writes
// the result as a field to the output. This is much slower
// than the type-specific functions.
func (jlb *JSONLogBuilder) Any(key string, val any) LogBuilder {
jlb.writeKey(key)
data, err := json.Marshal(val)
if err != nil {
panic(err)
}
jlb.out.Write(data)
return jlb
}
// Err adds an error as a field to the output
func (jlb *JSONLogBuilder) Err(err error) LogBuilder {
return jlb.Str("error", err.Error())
}
// Send sends the event to the output.
//
// After calling send, do not use the event again.
func (jlb *JSONLogBuilder) Send() {
jlb.out.WriteByte('}')
jlb.out.Flush()
if jlb.lvl == LogLevelFatal && !jlb.l.noExit {
os.Exit(1)
} else if jlb.lvl == LogLevelPanic && !jlb.l.noPanic {
panic("")
}
}

58
log/log.go Normal file
View File

@ -0,0 +1,58 @@
package log
import (
"logger"
"os"
)
var Logger logger.Logger = logger.NewJSON(os.Stderr)
// NoPanic prevents the logger from panicking on panic events
func NoPanic() {
Logger.NoPanic()
}
// NoExit prevents the logger from exiting on fatal events
func NoExit() {
Logger.NoExit()
}
// Debug creates a new debug event with the given message
func Debug(msg string) logger.LogBuilder {
return Logger.Debug(msg)
}
// Debugf creates a new debug event with the formatted message
func Debugf(format string, v ...any) logger.LogBuilder {
return Logger.Debugf(format, v...)
}
// Info creates a new info event with the given message
func Info(msg string) logger.LogBuilder {
return Logger.Info(msg)
}
// Infof creates a new info event with the formatted message
func Infof(format string, v ...any) logger.LogBuilder {
return Logger.Infof(format, v...)
}
// Warn creates a new warn event with the given message
func Warn(msg string) logger.LogBuilder {
return Logger.Warn(msg)
}
// Warnf creates a new warn event with the formatted message
func Warnf(format string, v ...any) logger.LogBuilder {
return Logger.Warnf(format, v...)
}
// Error creates a new error event with the given message
func Error(msg string) logger.LogBuilder {
return Logger.Error(msg)
}
// Errorf creates a new error event with the formatted message
func Errorf(format string, v ...any) logger.LogBuilder {
return Logger.Errorf(format, v...)
}

293
logger_test.go Normal file
View File

@ -0,0 +1,293 @@
package logger_test
import (
"bytes"
"encoding/json"
"errors"
"logger"
"os"
"runtime"
"testing"
"time"
)
var buf = &bytes.Buffer{}
var jsonlog = logger.NewJSON(buf)
var prettylog = logger.NewPretty(buf)
func TestJSON(t *testing.T) {
t.Run("empty", func(t *testing.T) {
jsonlog.Info("").Send()
if got, want := getStr(), `{"msg":"","level":"info"}`; got != want {
t.Errorf("got: %s, want: %s", got, want)
}
})
t.Run("one-field", func(t *testing.T) {
jsonlog.Info("Test").Int("n", 1234).Send()
if got, want := getStr(), `{"msg":"Test","level":"info","n":1234}`; got != want {
t.Errorf("got: %s, want: %s", got, want)
}
})
t.Run("two-field", func(t *testing.T) {
jsonlog.
Info("Test").
Int("n", 1234).
Float32("pi", 3.14).
Send()
if got, want := getStr(), `{"msg":"Test","level":"info","n":1234,"pi":3.14}`; got != want {
t.Errorf("got: %s, want: %s", got, want)
}
})
t.Run("any", func(t *testing.T) {
jsonlog.Info("Test").Any("any", nil).Send()
if got, want := getStr(), `{"msg":"Test","level":"info","any":null}`; got != want {
t.Errorf("got: %s, want: %s", got, want)
}
})
t.Run("all", func(t *testing.T) {
jsonlog.
Info("All").
Int("int", -1).
Int8("int8", -1).
Int16("int16", -1).
Int32("int32", -1).
Int64("int64", -1).
Uint("uint", 1).
Uint8("uint8", 1).
Uint16("uint16", 1).
Uint32("uint32", 1).
Uint64("uint64", 1).
Float32("float32", 3.14).
Float64("float64", 6.28).
Bool("bool", true).
Str("string", "").
Bytes("[]byte", []byte{0x12, 0x34, 0x56}).
Stringer("stringer", time.Second).
Any("any", nil).
Err(errors.New("err")).
Send()
if got, want := getStr(), `{"msg":"All","level":"info","int":-1,"int8":-1,"int16":-1,"int32":-1,"int64":-1,"uint":1,"uint8":1,"uint16":1,"uint32":1,"uint64":1,"float32":3.14,"float64":6.28,"bool":true,"string":"","[]byte":"EjRW","stringer":"1s","any":null,"error":"err"}`; got != want {
t.Errorf("got: %s, want: %s", got, want)
}
})
t.Run("decode", func(t *testing.T) {
var log struct {
Message string `json:"msg"`
Level string `json:"level"`
N int `json:"n"`
}
jsonlog.Info("Test").Int("n", 1234).Send()
err := json.Unmarshal(buf.Bytes(), &log)
if err != nil {
t.Error(err)
}
buf.Reset()
if log.Message != "Test" {
t.Errorf("expected message Test, got %s", log.Message)
}
if log.Level != "info" {
t.Errorf("expected level info, got %s", log.Level)
}
if log.N != 1234 {
t.Errorf("expected n 1234, got %d", log.N)
}
})
}
func TestPretty(t *testing.T) {
t.Run("empty", func(t *testing.T) {
prettylog.Info("").Send()
ctime := time.Now().Format(time.Kitchen)
if got, want := getStr(), ctime+" INF \n"; got != want {
t.Errorf("got: %s, want: %s", got, want)
}
})
t.Run("one-field", func(t *testing.T) {
prettylog.Info("Test").Int("n", 1234).Send()
ctime := time.Now().Format(time.Kitchen)
if got, want := getStr(), ctime+" INF Test n=1234\n"; got != want {
t.Errorf("got: %s, want: %s", got, want)
}
})
t.Run("two-field", func(t *testing.T) {
prettylog.
Info("Test").
Int("n", 1234).
Float32("pi", 3.14).
Send()
ctime := time.Now().Format(time.Kitchen)
if got, want := getStr(), ctime+" INF Test n=1234 pi=3.14\n"; got != want {
t.Errorf("got: %s, want: %s", got, want)
}
})
t.Run("any", func(t *testing.T) {
prettylog.Info("Test").Any("any", nil).Send()
ctime := time.Now().Format(time.Kitchen)
if got, want := getStr(), ctime+" INF Test any=null\n"; got != want {
t.Errorf("got: %s, want: %s", got, want)
}
})
t.Run("all", func(t *testing.T) {
prettylog.
Info("All").
Int("int", -1).
Int8("int8", -1).
Int16("int16", -1).
Int32("int32", -1).
Int64("int64", -1).
Uint("uint", 1).
Uint8("uint8", 1).
Uint16("uint16", 1).
Uint32("uint32", 1).
Uint64("uint64", 1).
Float32("float32", 3.14).
Float64("float64", 6.28).
Bool("bool", true).
Str("string", "").
Bytes("[]byte", []byte{0x12, 0x34, 0x56}).
Stringer("stringer", time.Second).
Any("any", nil).
Err(errors.New("err")).
Send()
ctime := time.Now().Format(time.Kitchen)
if got, want := getStr(), ctime+` INF All int=-1 int8=-1 int16=-1 int32=-1 int64=-1 uint=1 uint8=1 uint16=1 uint32=1 uint64=1 float32=3.14 float64=6.28 bool=true string="" []byte="123456" stringer="1s" any=null error="err"`+"\n"; got != want {
t.Errorf("got: %#v, want: %#v", got, want)
}
})
}
func BenchmarkJSON(b *testing.B) {
devnull, err := os.Open(getNullPath())
if err != nil {
b.Fatal(err)
}
jsonlog.Out = devnull
jsonlog.Level = logger.LogLevelDebug
b.Run("one-field", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
jsonlog.Debug("Benchmark").Int("int", 1)
}
})
b.Run("two-field", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
jsonlog.Debug("Benchmark").Int("int", 1).Float32("pi", 3.14)
}
})
b.Run("all", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
jsonlog.
Debug("All").
Int("int", -1).
Int8("int8", -1).
Int16("int16", -1).
Int32("int32", -1).
Int64("int64", -1).
Uint("uint", 1).
Uint8("uint8", 1).
Uint16("uint16", 1).
Uint32("uint32", 1).
Uint64("uint64", 1).
Float32("float32", 3.14).
Float64("float64", 6.28).
Bool("bool", true).
Str("string", "").
Bytes("[]byte", []byte{0x12, 0x34, 0x56}).
Stringer("stringer", time.Second).
Any("any", nil).
Err(errors.New("err")).
Send()
}
})
}
func BenchmarkPretty(b *testing.B) {
devnull, err := os.Open(getNullPath())
if err != nil {
b.Fatal(err)
}
prettylog.Out = devnull
prettylog.Level = logger.LogLevelDebug
b.Run("one-field", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
prettylog.Debug("Benchmark").Int("int", 1)
}
})
b.Run("two-field", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
prettylog.Debug("Benchmark").Int("int", 1).Float32("pi", 3.14)
}
})
b.Run("all", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
prettylog.
Debug("All").
Int("int", -1).
Int8("int8", -1).
Int16("int16", -1).
Int32("int32", -1).
Int64("int64", -1).
Uint("uint", 1).
Uint8("uint8", 1).
Uint16("uint16", 1).
Uint32("uint32", 1).
Uint64("uint64", 1).
Float32("float32", 3.14).
Float64("float64", 6.28).
Bool("bool", true).
Str("string", "").
Bytes("[]byte", []byte{0x12, 0x34, 0x56}).
Stringer("stringer", time.Second).
Any("any", nil).
Err(errors.New("err")).
Send()
}
})
}
func getStr() string {
defer buf.Reset()
return buf.String()
}
func getNullPath() string {
if runtime.GOOS == "windows" {
return "nul"
} else {
return "/dev/null"
}
}

130
main.go Normal file
View File

@ -0,0 +1,130 @@
package logger
import (
"fmt"
)
// LogLevel represents a log level
type LogLevel uint8
// Log levels
const (
LogLevelDebug LogLevel = iota
LogLevelInfo
LogLevelWarn
LogLevelError
LogLevelFatal
LogLevelPanic
)
var logLevelNames = [...]string{
LogLevelDebug: "debug",
LogLevelInfo: "info",
LogLevelWarn: "warn",
LogLevelError: "error",
LogLevelFatal: "fatal",
LogLevelPanic: "panic",
}
// Logger represents a logger
type Logger interface {
// NoPanic prevents the logger from panicking on panic events
NoPanic()
// NoExit prevents the logger from exiting on fatal events
NoExit()
// Debug creates a new debug event with the given message
Debug(string) LogBuilder
// Debugf creates a new debug event with the formatted message
Debugf(string, ...any) LogBuilder
// Info creates a new info event with the given message
Info(string) LogBuilder
// Infof creates a new info event with the formatted message
Infof(string, ...any) LogBuilder
// Warn creates a new warn event with the given message
Warn(string) LogBuilder
// Warnf creates a new warn event with the formatted message
Warnf(string, ...any) LogBuilder
// Error creates a new error event with the given message
Error(string) LogBuilder
// Errorf creates a new error event with the formatted message
Errorf(string, ...any) LogBuilder
// Fatal creates a new fatal event with the given message
//
// When sent, fatal events will cause a call to os.Exit(1)
Fatal(string) LogBuilder
// Fatalf creates a new fatal event with the formatted message
//
// When sent, fatal events will cause a call to os.Exit(1)
Fatalf(string, ...any) LogBuilder
// Panic creates a new panic event with the given message
//
// When sent, panic events will cause a panic
Panic(string) LogBuilder
// Panicf creates a new panic event with the formatted message
//
// When sent, panic events will cause a panic
Panicf(string, ...any) LogBuilder
}
// LogBuilder represents a log event builder
type LogBuilder interface {
// Int adds an int field to the output
Int(string, int) LogBuilder
// Int8 adds an int8 field to the output
Int8(string, int8) LogBuilder
// Int16 adds an int16 field to the output
Int16(string, int16) LogBuilder
// Int32 adds an int32 field to the output
Int32(string, int32) LogBuilder
// Int64 adds an int64 field to the output
Int64(string, int64) LogBuilder
// Uint adds a uint field to the output
Uint(string, uint) LogBuilder
// Uint8 adds a uint8 field to the output
Uint8(string, uint8) LogBuilder
// Uint16 adds a uint16 field to the output
Uint16(string, uint16) LogBuilder
// Uint32 adds a uint32 field to the output
Uint32(string, uint32) LogBuilder
// Uint64 adds a uint64 field to the output
Uint64(string, uint64) LogBuilder
// Float32 adds a float32 field to the output
Float32(string, float32) LogBuilder
// Float64 adds a float64 field to the output
Float64(string, float64) LogBuilder
// Stringer calls the String method of an fmt.Stringer
// and adds the resulting string as a field to the output
Stringer(string, fmt.Stringer) LogBuilder
// Bytes adds []byte as a field to the output
Bytes(string, []byte) LogBuilder
// Timestamp adds the time formatted as RFC3339Nano
// as a field to the output using the key "timestamp"
Timestamp() LogBuilder
// Bool adds a bool as a field to the output
Bool(string, bool) LogBuilder
// Str adds a string as a field to the output
Str(string, string) LogBuilder
// Any uses reflection to marshal any type and writes
// the result as a field to the output. This is much slower
// than the type-specific functions.
Any(string, any) LogBuilder
// Err adds an error as a field to the output
Err(error) LogBuilder
// Send sends the event to the output.
//
// After calling send, do not use the event again.
Send()
}

321
multi.go Normal file
View File

@ -0,0 +1,321 @@
package logger
import (
"fmt"
"os"
)
var _ Logger = (*MultiLogger)(nil)
// MultiLogger implements the Logger interface by
// writing to multiple underlying loggers sequentially.
type MultiLogger struct {
Loggers []Logger
noPanic bool
noExit bool
}
// NewMulti creates and returns a new MultiLogger
func NewMulti(l ...Logger) *MultiLogger {
for _, logger := range l {
logger.NoPanic()
logger.NoExit()
}
return &MultiLogger{Loggers: l}
}
// NoExit prevents the logger from exiting on fatal events
func (ml *MultiLogger) NoExit() {
ml.noExit = true
}
// NoPanic prevents the logger from panicking on panic events
func (ml *MultiLogger) NoPanic() {
ml.noPanic = true
}
// Debug creates a new debug event with the given message
func (ml *MultiLogger) Debug(msg string) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Debug(msg)
}
return &MultiLogBuilder{ml, lbs, LogLevelDebug}
}
// Debugf creates a new debug event with the formatted message
func (ml *MultiLogger) Debugf(format string, v ...any) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Debugf(format, v...)
}
return &MultiLogBuilder{ml, lbs, LogLevelDebug}
}
// Info creates a new info event with the given message
func (ml *MultiLogger) Info(msg string) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Info(msg)
}
return &MultiLogBuilder{ml, lbs, LogLevelInfo}
}
// Infof creates a new info event with the formatted message
func (ml *MultiLogger) Infof(format string, v ...any) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Infof(format, v...)
}
return &MultiLogBuilder{ml, lbs, LogLevelInfo}
}
// Warn creates a new warn event with the given message
func (ml *MultiLogger) Warn(msg string) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Warn(msg)
}
return &MultiLogBuilder{ml, lbs, LogLevelWarn}
}
// Warnf creates a new warn event with the formatted message
func (ml *MultiLogger) Warnf(format string, v ...any) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Warnf(format, v...)
}
return &MultiLogBuilder{ml, lbs, LogLevelWarn}
}
// Error creates a new error event with the given message
func (ml *MultiLogger) Error(msg string) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Error(msg)
}
return &MultiLogBuilder{ml, lbs, LogLevelError}
}
// Errorf creates a new error event with the formatted message
func (ml *MultiLogger) Errorf(format string, v ...any) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Errorf(format, v...)
}
return &MultiLogBuilder{ml, lbs, LogLevelError}
}
// Error creates a new error event with the given message
func (ml *MultiLogger) Fatal(msg string) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Fatal(msg)
}
return &MultiLogBuilder{ml, lbs, LogLevelFatal}
}
// Errorf creates a new error event with the formatted message
func (ml *MultiLogger) Fatalf(format string, v ...any) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Fatalf(format, v...)
}
return &MultiLogBuilder{ml, lbs, LogLevelFatal}
}
// Error creates a new error event with the given message
func (ml *MultiLogger) Panic(msg string) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Panic(msg)
}
return &MultiLogBuilder{ml, lbs, LogLevelPanic}
}
// Errorf creates a new error event with the formatted message
func (ml *MultiLogger) Panicf(format string, v ...any) LogBuilder {
lbs := make([]LogBuilder, len(ml.Loggers))
for index, logger := range ml.Loggers {
lbs[index] = logger.Panicf(format, v...)
}
return &MultiLogBuilder{ml, lbs, LogLevelPanic}
}
// MultiLogBuilder implements the LogBuilder interface
// by writing to multiple underlying LogBuilders sequentially.
type MultiLogBuilder struct {
l *MultiLogger
lbs []LogBuilder
lvl LogLevel
}
// Int adds an int field to the output
func (mlb *MultiLogBuilder) Int(key string, val int) LogBuilder {
for _, lb := range mlb.lbs {
lb.Int(key, val)
}
return mlb
}
// Int64 adds an int64 field to the output
func (mlb *MultiLogBuilder) Int64(key string, val int64) LogBuilder {
for _, lb := range mlb.lbs {
lb.Int64(key, val)
}
return mlb
}
// Int32 adds an int32 field to the output
func (mlb *MultiLogBuilder) Int32(key string, val int32) LogBuilder {
for _, lb := range mlb.lbs {
lb.Int32(key, val)
}
return mlb
}
// Int16 adds an int16 field to the output
func (mlb *MultiLogBuilder) Int16(key string, val int16) LogBuilder {
for _, lb := range mlb.lbs {
lb.Int16(key, val)
}
return mlb
}
// Int8 adds an int8 field to the output
func (mlb *MultiLogBuilder) Int8(key string, val int8) LogBuilder {
for _, lb := range mlb.lbs {
lb.Int8(key, val)
}
return mlb
}
// Uint adds a uint field to the output
func (mlb *MultiLogBuilder) Uint(key string, val uint) LogBuilder {
for _, lb := range mlb.lbs {
lb.Uint(key, val)
}
return mlb
}
// Uint64 adds a uint64 field to the output
func (mlb *MultiLogBuilder) Uint64(key string, val uint64) LogBuilder {
for _, lb := range mlb.lbs {
lb.Uint64(key, val)
}
return mlb
}
// Uint32 adds a uint32 field to the output
func (mlb *MultiLogBuilder) Uint32(key string, val uint32) LogBuilder {
for _, lb := range mlb.lbs {
lb.Uint32(key, val)
}
return mlb
}
// Uint16 adds a uint16 field to the output
func (mlb *MultiLogBuilder) Uint16(key string, val uint16) LogBuilder {
for _, lb := range mlb.lbs {
lb.Uint16(key, val)
}
return mlb
}
// Uint8 adds a uint8 field to the output
func (mlb *MultiLogBuilder) Uint8(key string, val uint8) LogBuilder {
for _, lb := range mlb.lbs {
lb.Uint8(key, val)
}
return mlb
}
// Float64 adds a float64 field to the output
func (mlb *MultiLogBuilder) Float64(key string, val float64) LogBuilder {
for _, lb := range mlb.lbs {
lb.Float64(key, val)
}
return mlb
}
// Float32 adds a float32 field to the output
func (mlb *MultiLogBuilder) Float32(key string, val float32) LogBuilder {
for _, lb := range mlb.lbs {
lb.Float32(key, val)
}
return mlb
}
// Stringer calls the String method of an fmt.Stringer
// and adds the resulting string as a field to the output
func (mlb *MultiLogBuilder) Stringer(key string, s fmt.Stringer) LogBuilder {
for _, lb := range mlb.lbs {
lb.Stringer(key, s)
}
return mlb
}
// Bytes writes base64-encoded bytes as a field to the output
func (mlb *MultiLogBuilder) Bytes(key string, b []byte) LogBuilder {
for _, lb := range mlb.lbs {
lb.Bytes(key, b)
}
return mlb
}
// Timestamp adds the time formatted as RFC3339Nano
// as a field to the output using the key "timestamp"
func (mlb *MultiLogBuilder) Timestamp() LogBuilder {
for _, lb := range mlb.lbs {
lb.Timestamp()
}
return mlb
}
// Bool adds a bool as a field to the output
func (mlb *MultiLogBuilder) Bool(key string, val bool) LogBuilder {
for _, lb := range mlb.lbs {
lb.Bool(key, val)
}
return mlb
}
// Str adds a string as a field to the output
func (mlb *MultiLogBuilder) Str(key, val string) LogBuilder {
for _, lb := range mlb.lbs {
lb.Str(key, val)
}
return mlb
}
// Any uses reflection to marshal any type and writes
// the result as a field to the output. This is much slower
// than the type-specific functions.
func (mlb *MultiLogBuilder) Any(key string, val any) LogBuilder {
for _, lb := range mlb.lbs {
lb.Any(key, val)
}
return mlb
}
// Err adds an error as a field to the output
func (mlb *MultiLogBuilder) Err(err error) LogBuilder {
for _, lb := range mlb.lbs {
lb.Err(err)
}
return mlb
}
// Send sends the event to the output.
//
// After calling send, do not use the event again.
func (mlb *MultiLogBuilder) Send() {
for _, lb := range mlb.lbs {
lb.Send()
}
if mlb.lvl == LogLevelFatal && !mlb.l.noExit {
os.Exit(1)
} else if mlb.lvl == LogLevelPanic && !mlb.l.noPanic {
panic("")
}
}

152
nop.go Normal file
View File

@ -0,0 +1,152 @@
package logger
import (
"fmt"
)
var _ Logger = (*NopLogger)(nil)
// NopLogger implements the Logger interface
// using human-readable output for log messages.
type NopLogger struct{}
// NewNop creates and returns a new NopLogger
func NewNop() NopLogger {
return NopLogger{}
}
// NoPanic prevents the logger from panicking on panic events
func (nl NopLogger) NoPanic() {}
// NoExit prevents the logger from exiting on fatal events
func (nl NopLogger) NoExit() {}
// Debug creates a new debug event with the given message
func (nl NopLogger) Debug(msg string) LogBuilder {
return NopLogBuilder{}
}
// Debugf creates a new debug event with the formatted message
func (nl NopLogger) Debugf(format string, v ...any) LogBuilder {
return NopLogBuilder{}
}
// Info creates a new info event with the given message
func (nl NopLogger) Info(msg string) LogBuilder {
return NopLogBuilder{}
}
// Infof creates a new info event with the formatted message
func (nl NopLogger) Infof(format string, v ...any) LogBuilder {
return NopLogBuilder{}
}
// Warn creates a new warn event with the given message
func (nl NopLogger) Warn(msg string) LogBuilder {
return NopLogBuilder{}
}
// Warnf creates a new warn event with the formatted message
func (nl NopLogger) Warnf(format string, v ...any) LogBuilder {
return NopLogBuilder{}
}
// Error creates a new error event with the given message
func (nl NopLogger) Error(msg string) LogBuilder {
return NopLogBuilder{}
}
// Errorf creates a new error event with the formatted message
func (nl NopLogger) Errorf(format string, v ...any) LogBuilder {
return NopLogBuilder{}
}
// Fatal creates a new fatal event with the given message
func (nl NopLogger) Fatal(msg string) LogBuilder {
return NopLogBuilder{}
}
// Fatalf creates a new fatal event with the formatted message
func (nl NopLogger) Fatalf(format string, v ...any) LogBuilder {
return NopLogBuilder{}
}
// Panic creates a new panic event with the given message
func (nl NopLogger) Panic(msg string) LogBuilder {
return NopLogBuilder{}
}
// Panicf creates a new panic event with the formatted message
func (nl NopLogger) Panicf(format string, v ...any) LogBuilder {
return NopLogBuilder{}
}
// NopLogBuilder implements the LogBuilder interface
// using human-readable output for log messages
type NopLogBuilder struct{}
// Int adds an int field to the output
func (nlb NopLogBuilder) Int(key string, val int) LogBuilder { return nlb }
// Int64 adds an int64 field to the output
func (nlb NopLogBuilder) Int64(key string, val int64) LogBuilder { return nlb }
// Int32 adds an int32 field to the output
func (nlb NopLogBuilder) Int32(key string, val int32) LogBuilder { return nlb }
// Int16 adds an int16 field to the output
func (nlb NopLogBuilder) Int16(key string, val int16) LogBuilder { return nlb }
// Int8 adds an int8 field to the output
func (nlb NopLogBuilder) Int8(key string, val int8) LogBuilder { return nlb }
// Uint adds a uint field to the output
func (nlb NopLogBuilder) Uint(key string, val uint) LogBuilder { return nlb }
// Uint64 adds a uint64 field to the output
func (nlb NopLogBuilder) Uint64(key string, val uint64) LogBuilder { return nlb }
// Uint32 adds a uint32 field to the output
func (nlb NopLogBuilder) Uint32(key string, val uint32) LogBuilder { return nlb }
// Uint16 adds a uint16 field to the output
func (nlb NopLogBuilder) Uint16(key string, val uint16) LogBuilder { return nlb }
// Uint8 adds a uint8 field to the output
func (nlb NopLogBuilder) Uint8(key string, val uint8) LogBuilder { return nlb }
// Float64 adds a float64 field to the output
func (nlb NopLogBuilder) Float64(key string, val float64) LogBuilder { return nlb }
// Float32 adds a float32 field to the output
func (nlb NopLogBuilder) Float32(key string, val float32) LogBuilder { return nlb }
// Stringer calls the String method of an fmt.Stringer
// and adds the resulting string as a field to the output
func (nlb NopLogBuilder) Stringer(key string, s fmt.Stringer) LogBuilder { return nlb }
// Bytes writes hex-encoded bytes as a field to the output
func (nlb NopLogBuilder) Bytes(key string, b []byte) LogBuilder { return nlb }
// Timestamp adds the time formatted as RFC3339Nano
// as a field to the output
func (nlb NopLogBuilder) Timestamp() LogBuilder { return nlb }
// Bool adds a bool as a field to the output
func (nlb NopLogBuilder) Bool(key string, val bool) LogBuilder { return nlb }
// Str adds a string as a field to the output
func (nlb NopLogBuilder) Str(key, val string) LogBuilder { return nlb }
// Any uses reflection to marshal any type and writes
// the result as a field to the output. This is much slower
// than the type-specific functions.
func (nlb NopLogBuilder) Any(key string, val any) LogBuilder { return nlb }
// Err adds an error as a field to the output
func (nlb NopLogBuilder) Err(err error) LogBuilder { return nlb }
// Send sends the event to the output.
//
// After calling send, do not use the event again.
func (nlb NopLogBuilder) Send() {}

348
pretty.go Normal file
View File

@ -0,0 +1,348 @@
package logger
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"time"
"github.com/gookit/color"
"github.com/mattn/go-isatty"
)
var _ Logger = (*PrettyLogger)(nil)
// PrettyLogger implements the Logger interface
// using human-readable output for log messages.
type PrettyLogger struct {
Out io.Writer
Level LogLevel
TimeFormat string
UseColor bool
TimeColor color.Color
MsgColor color.Color
KeyColor color.Color
DebugColor color.Color
InfoColor color.Color
WarnColor color.Color
ErrColor color.Color
FatalColor color.Color
PanicColor color.Color
noPanic bool
noExit bool
}
// NewPretty creates and returns a new PrettyLogger.
// If the input writer is io.Discard, NopLogger
// will be returned.
func NewPretty(out io.Writer) *PrettyLogger {
useColor := false
if f, ok := out.(*os.File); ok {
useColor = isatty.IsTerminal(f.Fd())
}
return &PrettyLogger{
Out: out,
Level: LogLevelInfo,
TimeFormat: time.Kitchen,
UseColor: useColor,
TimeColor: color.FgGray,
MsgColor: color.Normal,
KeyColor: color.FgCyan,
DebugColor: color.FgYellow,
InfoColor: color.FgGreen,
WarnColor: color.FgRed,
ErrColor: color.FgLightRed,
FatalColor: color.FgRed.Darken(),
PanicColor: color.FgRed.Darken(),
}
}
// NoPanic prevents the logger from panicking on panic events
func (pl *PrettyLogger) NoPanic() {
pl.noPanic = true
}
// NoExit prevents the logger from exiting on fatal events
func (pl *PrettyLogger) NoExit() {
pl.noExit = true
}
// Debug creates a new debug event with the given message
func (pl *PrettyLogger) Debug(msg string) LogBuilder {
return newPrettyLogBuilder(pl, msg, LogLevelDebug)
}
// Debugf creates a new debug event with the formatted message
func (pl *PrettyLogger) Debugf(format string, v ...any) LogBuilder {
return newPrettyLogBuilder(pl, fmt.Sprintf(format, v...), LogLevelDebug)
}
// Info creates a new info event with the given message
func (pl *PrettyLogger) Info(msg string) LogBuilder {
return newPrettyLogBuilder(pl, msg, LogLevelInfo)
}
// Infof creates a new info event with the formatted message
func (pl *PrettyLogger) Infof(format string, v ...any) LogBuilder {
return newPrettyLogBuilder(pl, fmt.Sprintf(format, v...), LogLevelInfo)
}
// Warn creates a new warn event with the given message
func (pl *PrettyLogger) Warn(msg string) LogBuilder {
return newPrettyLogBuilder(pl, msg, LogLevelWarn)
}
// Warnf creates a new warn event with the formatted message
func (pl *PrettyLogger) Warnf(format string, v ...any) LogBuilder {
return newPrettyLogBuilder(pl, fmt.Sprintf(format, v...), LogLevelWarn)
}
// Error creates a new error event with the given message
func (pl *PrettyLogger) Error(msg string) LogBuilder {
return newPrettyLogBuilder(pl, msg, LogLevelError)
}
// Errorf creates a new error event with the formatted message
func (pl *PrettyLogger) Errorf(format string, v ...any) LogBuilder {
return newPrettyLogBuilder(pl, fmt.Sprintf(format, v...), LogLevelError)
}
// Fatal creates a new fatal event with the given message
//
// When sent, fatal events will cause a call to os.Exit(1)
func (pl *PrettyLogger) Fatal(msg string) LogBuilder {
return newPrettyLogBuilder(pl, msg, LogLevelFatal)
}
// Fatalf creates a new fatal event with the formatted message
//
// When sent, fatal events will cause a call to os.Exit(1)
func (pl *PrettyLogger) Fatalf(format string, v ...any) LogBuilder {
return newPrettyLogBuilder(pl, fmt.Sprintf(format, v...), LogLevelFatal)
}
// Panic creates a new panic event with the given message
//
// When sent, panic events will cause a panic
func (pl *PrettyLogger) Panic(msg string) LogBuilder {
return newPrettyLogBuilder(pl, msg, LogLevelPanic)
}
// Panicf creates a new panic event with the formatted message
//
// When sent, panic events will cause a panic
func (pl *PrettyLogger) Panicf(format string, v ...any) LogBuilder {
return newPrettyLogBuilder(pl, fmt.Sprintf(format, v...), LogLevelPanic)
}
// PrettyLogBuilder implements the LogBuilder interface
// using human-readable output for log messages
type PrettyLogBuilder struct {
l *PrettyLogger
lvl LogLevel
out writer
}
func newPrettyLogBuilder(pl *PrettyLogger, msg string, lvl LogLevel) LogBuilder {
if pl.Out == io.Discard || lvl < pl.Level {
return NopLogBuilder{}
}
lb := &PrettyLogBuilder{
l: pl,
out: writer{&bytes.Buffer{}, pl.Out},
lvl: lvl,
}
lb.writeColor(lb.l.TimeColor, time.Now().Format(lb.l.TimeFormat))
lb.out.WriteByte(' ')
switch lvl {
case LogLevelDebug:
lb.writeColor(lb.l.DebugColor, "DBG")
case LogLevelInfo:
lb.writeColor(lb.l.InfoColor, "INF")
case LogLevelWarn:
lb.writeColor(lb.l.WarnColor, "WRN")
case LogLevelError:
lb.writeColor(lb.l.ErrColor, "ERR")
case LogLevelFatal:
lb.writeColor(lb.l.FatalColor, "FTL")
case LogLevelPanic:
lb.writeColor(lb.l.PanicColor, "PNC")
}
lb.out.WriteByte(' ')
lb.writeColor(lb.l.MsgColor, msg)
return lb
}
// writeKey writes a JSON key to the buffer
func (plb *PrettyLogBuilder) writeKey(k string) {
plb.out.WriteByte(' ')
plb.writeColor(plb.l.KeyColor, k)
plb.out.WriteByte('=')
}
// Int adds an int field to the output
func (plb *PrettyLogBuilder) Int(key string, val int) LogBuilder {
return plb.Int64(key, int64(val))
}
// Int64 adds an int64 field to the output
func (plb *PrettyLogBuilder) Int64(key string, val int64) LogBuilder {
plb.writeKey(key)
plb.out.WriteString(strconv.FormatInt(val, 10))
return plb
}
// Int32 adds an int32 field to the output
func (plb *PrettyLogBuilder) Int32(key string, val int32) LogBuilder {
return plb.Int64(key, int64(val))
}
// Int16 adds an int16 field to the output
func (plb *PrettyLogBuilder) Int16(key string, val int16) LogBuilder {
return plb.Int64(key, int64(val))
}
// Int8 adds an int8 field to the output
func (plb *PrettyLogBuilder) Int8(key string, val int8) LogBuilder {
return plb.Int64(key, int64(val))
}
// Uint adds a uint field to the output
func (plb *PrettyLogBuilder) Uint(key string, val uint) LogBuilder {
return plb.Uint64(key, uint64(val))
}
// Uint64 adds a uint64 field to the output
func (plb *PrettyLogBuilder) Uint64(key string, val uint64) LogBuilder {
plb.writeKey(key)
plb.out.WriteString(strconv.FormatUint(val, 10))
return plb
}
// Uint32 adds a uint32 field to the output
func (plb *PrettyLogBuilder) Uint32(key string, val uint32) LogBuilder {
return plb.Uint64(key, uint64(val))
}
// Uint16 adds a uint16 field to the output
func (plb *PrettyLogBuilder) Uint16(key string, val uint16) LogBuilder {
return plb.Uint64(key, uint64(val))
}
// Uint8 adds a uint8 field to the output
func (plb *PrettyLogBuilder) Uint8(key string, val uint8) LogBuilder {
return plb.Uint64(key, uint64(val))
}
// float adds a float of specified bitsize to the output
func (plb *PrettyLogBuilder) float(key string, val float64, bitsize int) LogBuilder {
plb.writeKey(key)
plb.out.WriteString(strconv.FormatFloat(val, 'f', -1, bitsize))
return plb
}
// Float64 adds a float64 field to the output
func (plb *PrettyLogBuilder) Float64(key string, val float64) LogBuilder {
return plb.float(key, val, 64)
}
// Float32 adds a float32 field to the output
func (plb *PrettyLogBuilder) Float32(key string, val float32) LogBuilder {
return plb.float(key, float64(val), 32)
}
// Stringer calls the String method of an fmt.Stringer
// and adds the resulting string as a field to the output
func (plb *PrettyLogBuilder) Stringer(key string, s fmt.Stringer) LogBuilder {
return plb.Str(key, s.String())
}
// Bytes writes hex-encoded bytes as a field to the output
func (plb *PrettyLogBuilder) Bytes(key string, b []byte) LogBuilder {
return plb.Str(key, hex.EncodeToString(b))
}
// Timestamp adds the time formatted as RFC3339Nano
// as a field to the output
func (plb *PrettyLogBuilder) Timestamp() LogBuilder {
return plb.Str("timestamp", time.Now().Format(time.RFC3339Nano))
}
// Bool adds a bool as a field to the output
func (plb *PrettyLogBuilder) Bool(key string, val bool) LogBuilder {
plb.writeKey(key)
if val {
plb.out.WriteString("true")
} else {
plb.out.WriteString("false")
}
return plb
}
// Str adds a string as a field to the output
func (plb *PrettyLogBuilder) Str(key, val string) LogBuilder {
plb.writeKey(key)
plb.out.WriteByte('"')
plb.out.WriteString(val)
plb.out.WriteByte('"')
return plb
}
// Any uses reflection to marshal any type and writes
// the result as a field to the output. This is much slower
// than the type-specific functions.
func (plb *PrettyLogBuilder) Any(key string, val any) LogBuilder {
plb.writeKey(key)
data, err := json.Marshal(val)
if err != nil {
panic(err)
}
plb.out.Write(data)
return plb
}
// Err adds an error as a field to the output
func (plb *PrettyLogBuilder) Err(err error) LogBuilder {
plb.out.WriteByte(' ')
plb.writeColor(plb.l.ErrColor, "error=")
plb.writeColor(plb.l.ErrColor, `"`+err.Error()+`"`)
return plb
}
// Send sends the event to the output.
//
// After calling send, do not use the event again.
func (plb *PrettyLogBuilder) Send() {
plb.out.WriteByte('\n')
plb.out.Flush()
if plb.lvl == LogLevelFatal && !plb.l.noExit {
os.Exit(1)
} else if plb.lvl == LogLevelPanic && !plb.l.noPanic {
panic("")
}
}
// writeColor writes a string to the buffer using the given color
func (plb *PrettyLogBuilder) writeColor(c color.Color, s string) {
if plb.l.UseColor {
plb.out.WriteString(c.Text(s))
} else {
plb.out.WriteString(s)
}
}

22
writer.go Normal file
View File

@ -0,0 +1,22 @@
package logger
import (
"bytes"
"io"
)
// writer combines a buffer and a writer,
// adding a flush function to flush the buffer
// to the underlying writer. This is used
// to avoid file I/O, making the logger faster.
type writer struct {
*bytes.Buffer
w io.Writer
}
// Flush writes the buffer contents to the
// underlying writer
func (w writer) Flush() error {
_, err := io.Copy(w.w, w.Buffer)
return err
}