wtflog

package module
v0.0.0-...-6ec5f38 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 24, 2022 License: MulanPSL-2.0 Imports: 11 Imported by: 0

README

Wtflog

Wtflog is a logging library that provides convenient logging APIs like standard log and logrus, but more efficient in recording complex structures (with gen-bsa).

Usage

Basic
package main

import "gitee.com/FlyingOnion/wtflog"

func main() {
    logger := wtflog.NewTextLogger()
    logger.Info("hello world")
    // output:
    // hello world
}
Struct, Map, Slice

For complex structures (struct, map, slice), log them with gen-bsa:

  1. go install gitee.com/FlyingOnion/gen-bsa
  2. cd to root directory and run gen-bsa, and the code generator will generate helper functions AppendTo in wtf__.go file
  3. use logging functions
type FooStruct struct {
    S string
    I int
}

type FooSlice []FooStruct

type FooMap map[string]FooStruct

Part of generated code:

func (p FooStruct) AppendTo(b *ByteSlice) {
	var any interface{}
	_ = any
	b.AppendString("{S:")
	b.AppendString(p.S)
	b.AppendString(" I:")
	b.AppendInt64(int64(p.I))
	b.AppendByte('}')
}

func (p FooSlice) AppendTo(b *ByteSlice) {
	if p == nil { b.AppendString("<nil>"); return }
	var any interface{}
	_ = any
	b.AppendByte('[')
	for i, elem := range p {
		if i > 0 { b.AppendByte(' ') }
		any = elem
		if bsa, ok := any.(ByteSliceAppender); ok { bsa.AppendTo(b) } else if str, ok := any.(Stringer); ok { b.AppendString(str.String()) } else { b.AppendString(fmtPrint(any)) }
	}
	b.AppendByte(']')
}

func (p FooMap) AppendTo(b *ByteSlice) {
	if p == nil { b.AppendString("<nil>"); return }
	if len(p) == 0 { b.AppendString("{}"); return }
	var any interface{}
	_ = any
	b.AppendByte('{')
	kvs := make([]struct{k string; v interface{}}, 0, len(p))
	for k, v := range p { kvs = append(kvs, struct{k string; v interface{}}{k: string(k), v: interface{}(v)}) }
	sortKeys(kvs, func(i, j int) bool { return kvs[i].k < kvs[j].k })
	for i, kv := range kvs {
		if i > 0 { b.AppendByte(' ') }
		b.AppendString(kv.k)
		b.AppendByte(':')
		any = kv.v
		if bsa, ok := any.(ByteSliceAppender); ok { bsa.AppendTo(b) } else if str, ok := any.(Stringer); ok { b.AppendString(str.String()) } else { b.AppendString(fmtPrint(any)) }
	}
	b.AppendByte('}')
}
package main

import "gitee.com/FlyingOnion/wtflog"

func main() {
	foo := FooStruct{S: "hello", I: 8}
	fooSlice := FooSlice([]FooStruct{foo})
	fooMap := FooMap(map[string]FooStruct{"foo": foo})
	l := wtflog.NewTextLogger()
	l.Info(foo, fooSlice, fooMap)
    // output:
    // {S:hello I:8} [{S:hello I:8}] {foo:{S:hello I:8}}
}
With Field Log Level, Time, and Caller

We provide WithFieldsAtFront and WithFieldsAtBack function if you want to add fields.

Available fields are: FieldCaller, FieldLevel, FieldTime. All fields are formatted in default way "key=value"

l := wtflog.NewTextLogger().WithFieldsAtFront(FieldLevel, FieldTime).WithFieldsAtBack(FieldCaller)
l.Info("hello world")
// output:
// level=info time=2022-05-23 10:03:21 hello world caller=c:/Users/Administrator/go/src/.../logger_test.go:22
Logger Initialization

We suggest you init and set global logger if the logger is used widely in your program.

import "gitee.com/FlyingOnion/wtflog"

func InitLogger() {
    // Available options:
    // WithFieldsAtFront
    // WithFieldsAtBack
    // WithCallerSkip
    logger := wtflog.NewTextLogger().
        WithLogLevel(wtflog.LevelDebug).
        WithWriter(yourWriter)
    wtflog.SetGlobalLogger(logger)
    // you can use wtflog.Info, wtflog.Error directly with your custom logger now.
}

Work Progress

  • text logger: 80%
  • json logger: 80%
  • formatted logger (f methods): TODO
  • code generator gen-bsa: 90%

Why Another Logging Library

We researched current logging libraries (including popular logrus, go-kit/log, zap, zerolog), and found a disappointing phenomenon. Almost all libraries (even those claiming zero allocations) failed to find a more effective way to record complex structures (struct, map, slice, array, pointer). All they do is to pass the arguments to fmt.Sprint or fmt.Sprintf.

In wtflog, things will be changed.

The design philosophy of wtflog is to move the place that decides logging format of a variable from fmt to the variable itself as much as possible. This method is quite different from convention, and you may need to take some time to adapt it.

import "gitee.com/FlyingOnion/wtflog"
import . "gitee.com/FlyingOnion/wtflog/types"

func Log() {
    logger := wtflog.NewTextLogger()
    logger.Info("Hello world")
    logger.Info(1)      // bad
    logger.Info(Int(1)) // good
    logger.Info(time.Now())       // bad
    logger.Info(Time(time.Now())) // good
}

In the example above, 1 is an integer and it does not bring any information about how it will be logged (it will be decided by fmt). As a contrast, Int type is a custom type that brings these information. See the declaration of Int:

type Int int
func (i Int) AppendTo(b *ByteSlice) { b.AppendInt64(int64(i)) }
func (i Int) String() string        { return strconv.FormatInt(int64(i), 10) }

The AppendTo function implements ByteSliceAppender interface, which could be distinguished by wtflog, so that it could skip fmt type check and directly append to logging buffer b. This method is similar with what zap and zerolog do.

The type-check process of wtflog is quite simple. Suppose the argument is v (interface{})

  1. If v is nil, write <nil>.
  2. Check whether v is a []byte, string, ByteSliceAppender, Stringer successively. If matched then use corresponding write method.
  3. If either is not matched use fmt.Sprintf("%+v", v) and write that string.

We imagine that if each argument is either a []byte, string, ByteSliceAppender, Stringer, the logging efficiency will be improved dramatically, compared with standard log and logrus.

For basic types (except string and []byte), we provide the Int and Uint family, Float, Time, and Duration.

// Assume that you have already
// import . "gitee.com/FlyingOnion/wtflog/types"

logger.Info(Int(1)) // or Int8(1), Int16(1), Int32(1), Int64(1)
logger.Info(Uint(1)) // or Uint8(1), Uint16(1), Uint32(1), Uint64(1)
logger.Info(Float32(1.0)) // or Float64(1.0)
logger.Info(Float32f(1.0, 'e', 2)) // or Float64f with format and precision
logger.Info(Time(time.Now())) // "2006-01-02 15:04:05" format
logger.Info(Timef(time.Now(), time.RFC3339)) // RFC3339 format
logger.Info(Timestamp(time.Now())) // or TimestampMilli, TimestampMicro, TimestampNano
logger.Info(Duration(1*time.Second))
logger.Info(Error(io.EOF))

Level Writer

The concept of level writer is similar with the one of logrus. We provide a builtin level writer. Usage of level writer is shown below.

// set os.Stdout as default writer.
// set a level writer map.
levelWriter := NewLevelWriter(os.Stdout, map[wtflog.Level]io.Writer{
    wtflog.LevelWarn:  os.Stderr,
    wtflog.LevelError: os.Stderr,
})
logger := NewTextLogger().WithWriter(levelWriter)

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// FieldCaller adds a caller field.
	// To set caller skip level, use WithCallerSkip.
	FieldCaller fieldCaller
	FieldLevel  fieldLevel
	FieldTime   fieldTime

	FieldTimestamp TimeFormatFunc = func(t time.Time) string {
		return strconv.FormatInt(t.Unix(), 10)
	}

	FieldTimestampNano TimeFormatFunc = func(t time.Time) string {
		return strconv.FormatInt(t.UnixNano(), 10)
	}
)
View Source
var (
	LevelMap = map[Level]string{
		LevelDebug:   "debug",
		LevelInfo:    "info",
		LevelWarn:    "warn",
		LevelError:   "error",
		LevelFatal:   "fatal",
		LevelUnknown: "unknown",
	}
)

Functions

func SetGlobalLogger

func SetGlobalLogger(logger Logger)

Types

type AA

type AA struct {
	S  string
	I  int
	Bs []BB
}

func (AA) AppendTo

func (p AA) AppendTo(b *ByteSlice)

type BB

type BB struct {
	M string
	N []string
}

func (BB) AppendTo

func (p BB) AppendTo(b *ByteSlice)

type Event

type Event struct {
	CallerSkip int
	Level      Level
	Time       time.Time
}

func (Event) AppendTo

func (p Event) AppendTo(b *ByteSlice)

type Field

type Field interface {
	// ApplyTo returns the raw value of field.
	ApplyTo(Event) string
	Key() string
}

type JsonLogger

type JsonLogger struct {
	// contains filtered or unexported fields
}

func NewJsonLogger

func NewJsonLogger() *JsonLogger

func (JsonLogger) AppendTo

func (p JsonLogger) AppendTo(b *ByteSlice)

func (*JsonLogger) Copy

func (l *JsonLogger) Copy() *JsonLogger

func (*JsonLogger) Error

func (l *JsonLogger) Error(vs ...interface{})

func (*JsonLogger) Info

func (l *JsonLogger) Info(vs ...interface{})

func (*JsonLogger) Infof

func (l *JsonLogger) Infof(format string, vs ...interface{})

func (*JsonLogger) Warn

func (l *JsonLogger) Warn(vs ...interface{})

func (*JsonLogger) WithCallerSkip

func (l *JsonLogger) WithCallerSkip(skip int) *JsonLogger

func (*JsonLogger) WithFieldsAtBack

func (l *JsonLogger) WithFieldsAtBack(fields ...Field) *JsonLogger

func (*JsonLogger) WithFieldsAtFront

func (l *JsonLogger) WithFieldsAtFront(fields ...Field) *JsonLogger

func (*JsonLogger) WithLogLevel

func (l *JsonLogger) WithLogLevel(level Level) *JsonLogger

func (*JsonLogger) WithWriter

func (l *JsonLogger) WithWriter(w io.Writer) *JsonLogger

type Level

type Level uint8
const (
	LevelDebug Level = 1 + iota
	LevelInfo
	LevelWarn
	LevelError
	LevelFatal

	LevelUnknown Level = 99
)

func (Level) AppendTo

func (p Level) AppendTo(b *ByteSlice)

type LevelWriter

type LevelWriter interface {
	Write(p []byte) (int, error)
	WriteLevel(level Level, p []byte) (int, error)
}

LevelWriter is a concept "copied" from logrus. We provide a builtin type wtfLevelWriter to implement this interface. Use NewLevelWriter in the logger if needed.

func NewLevelWriter

func NewLevelWriter(defaultWriter io.Writer, levelWriterMap map[Level]io.Writer) LevelWriter

NewLevelWriter inits a level writer with defaultWriter and levelWriterMap. If levelWriterMap is nil or empty then all messages are logged to defaultWriter. If you only want to log all to stdout/stderr, use WithWriter(os.Stdout/os.Stderr) directly.

type Logger

type Logger interface {
	Info(vs ...interface{})
	Warn(vs ...interface{})
	Error(vs ...interface{})
}

func GlobalLogger

func GlobalLogger() Logger

type TextLogger

type TextLogger struct {
	// contains filtered or unexported fields
}

func NewTextLogger

func NewTextLogger() *TextLogger

NewTextLogger inits a TextLogger.

func (TextLogger) AppendTo

func (p TextLogger) AppendTo(b *ByteSlice)

func (*TextLogger) Copy

func (l *TextLogger) Copy() *TextLogger

func (*TextLogger) Error

func (l *TextLogger) Error(vs ...interface{})

func (*TextLogger) Info

func (l *TextLogger) Info(vs ...interface{})

func (*TextLogger) Infof

func (l *TextLogger) Infof(format string, vs ...interface{})

func (*TextLogger) Warn

func (l *TextLogger) Warn(vs ...interface{})

func (*TextLogger) WithCallerSkip

func (l *TextLogger) WithCallerSkip(skip int) *TextLogger

func (*TextLogger) WithFieldsAtBack

func (l *TextLogger) WithFieldsAtBack(fields ...TextLoggerField) *TextLogger

func (*TextLogger) WithFieldsAtFront

func (l *TextLogger) WithFieldsAtFront(fields ...TextLoggerField) *TextLogger

func (*TextLogger) WithLogLevel

func (l *TextLogger) WithLogLevel(level Level) *TextLogger

func (*TextLogger) WithWriter

func (l *TextLogger) WithWriter(w io.Writer) *TextLogger

type TextLoggerField

type TextLoggerField interface {
	Field
	Prefix() string
	Suffix() string
}

type TimeFormatFunc

type TimeFormatFunc func(t time.Time) string

func (TimeFormatFunc) ApplyTo

func (fn TimeFormatFunc) ApplyTo(e Event) string

func (TimeFormatFunc) Key

func (TimeFormatFunc) Key() string

func (TimeFormatFunc) Prefix

func (fn TimeFormatFunc) Prefix() string

func (TimeFormatFunc) Suffix

func (fn TimeFormatFunc) Suffix() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL