Add logger package with performance benchmarks
This commit is contained in:
278
logger/logger.go
Normal file
278
logger/logger.go
Normal file
@@ -0,0 +1,278 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Level int8
|
||||
|
||||
const (
|
||||
DEBUG Level = iota
|
||||
INFO
|
||||
WARN
|
||||
ERROR
|
||||
FATAL
|
||||
PANIC
|
||||
)
|
||||
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case DEBUG:
|
||||
return "DEBUG"
|
||||
case INFO:
|
||||
return "INFO"
|
||||
case WARN:
|
||||
return "WARN"
|
||||
case ERROR:
|
||||
return "ERROR"
|
||||
case FATAL:
|
||||
return "FATAL"
|
||||
case PANIC:
|
||||
return "PANIC"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
type Format int8
|
||||
|
||||
const (
|
||||
TEXT Format = iota
|
||||
JSON
|
||||
)
|
||||
|
||||
type Output int8
|
||||
|
||||
const (
|
||||
Stdout Output = iota
|
||||
Stderr
|
||||
)
|
||||
|
||||
type Entry struct {
|
||||
Time time.Time
|
||||
Level Level
|
||||
Message string
|
||||
Fields map[string]interface{}
|
||||
Caller string
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
opts options
|
||||
mu sync.Mutex
|
||||
writer io.Writer
|
||||
formatter Formatter
|
||||
}
|
||||
|
||||
func New(opts ...Option) *Logger {
|
||||
o := defaultOptions()
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
|
||||
var writer io.Writer
|
||||
switch o.output {
|
||||
case Stdout:
|
||||
writer = os.Stdout
|
||||
case Stderr:
|
||||
writer = os.Stderr
|
||||
default:
|
||||
writer = os.Stdout
|
||||
}
|
||||
|
||||
if o.outputFile != "" {
|
||||
f, err := os.OpenFile(o.outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err == nil {
|
||||
writer = f
|
||||
}
|
||||
}
|
||||
|
||||
return &Logger{
|
||||
opts: o,
|
||||
writer: writer,
|
||||
formatter: newFormatter(o.format),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) log(level Level, msg string, args ...interface{}) {
|
||||
if level < l.opts.level {
|
||||
return
|
||||
}
|
||||
|
||||
entry := &Entry{
|
||||
Time: time.Now(),
|
||||
Level: level,
|
||||
Message: fmt.Sprintf(msg, args...),
|
||||
Fields: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if l.opts.caller {
|
||||
entry.Caller = getCaller(3)
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.formatter.Format(l.writer, entry)
|
||||
}
|
||||
|
||||
func (l *Logger) logCtx(level Level, ctx context.Context, msg string, args ...interface{}) {
|
||||
if level < l.opts.level {
|
||||
return
|
||||
}
|
||||
|
||||
entry := &Entry{
|
||||
Time: time.Now(),
|
||||
Level: level,
|
||||
Message: fmt.Sprintf(msg, args...),
|
||||
Fields: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if ctx != nil {
|
||||
if traceID := ctx.Value(l.opts.fieldKey); traceID != nil {
|
||||
entry.Fields[l.opts.fieldKey] = traceID
|
||||
}
|
||||
}
|
||||
|
||||
if l.opts.caller {
|
||||
entry.Caller = getCaller(3)
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.formatter.Format(l.writer, entry)
|
||||
}
|
||||
|
||||
func (l *Logger) logField(level Level, message string, keyValues ...interface{}) {
|
||||
if level < l.opts.level {
|
||||
return
|
||||
}
|
||||
|
||||
entry := &Entry{
|
||||
Time: time.Now(),
|
||||
Level: level,
|
||||
Message: message,
|
||||
Fields: parseFields(keyValues),
|
||||
}
|
||||
|
||||
if l.opts.caller {
|
||||
entry.Caller = getCaller(3)
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.formatter.Format(l.writer, entry)
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(format string, args ...interface{}) {
|
||||
l.log(DEBUG, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Info(format string, args ...interface{}) {
|
||||
l.log(INFO, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(format string, args ...interface{}) {
|
||||
l.log(WARN, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(format string, args ...interface{}) {
|
||||
l.log(ERROR, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatal(format string, args ...interface{}) {
|
||||
l.log(FATAL, format, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Panic(format string, args ...interface{}) {
|
||||
l.log(PANIC, format, args...)
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) DebugCtx(ctx context.Context, format string, args ...interface{}) {
|
||||
l.logCtx(DEBUG, ctx, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) InfoCtx(ctx context.Context, format string, args ...interface{}) {
|
||||
l.logCtx(INFO, ctx, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) WarnCtx(ctx context.Context, format string, args ...interface{}) {
|
||||
l.logCtx(WARN, ctx, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) ErrorCtx(ctx context.Context, format string, args ...interface{}) {
|
||||
l.logCtx(ERROR, ctx, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) FatalCtx(ctx context.Context, format string, args ...interface{}) {
|
||||
l.logCtx(FATAL, ctx, format, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) PanicCtx(ctx context.Context, format string, args ...interface{}) {
|
||||
l.logCtx(PANIC, ctx, format, args...)
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) DebugField(message string, keyValues ...interface{}) {
|
||||
l.logField(DEBUG, message, keyValues...)
|
||||
}
|
||||
|
||||
func (l *Logger) InfoField(message string, keyValues ...interface{}) {
|
||||
l.logField(INFO, message, keyValues...)
|
||||
}
|
||||
|
||||
func (l *Logger) WarnField(message string, keyValues ...interface{}) {
|
||||
l.logField(WARN, message, keyValues...)
|
||||
}
|
||||
|
||||
func (l *Logger) ErrorField(message string, keyValues ...interface{}) {
|
||||
l.logField(ERROR, message, keyValues...)
|
||||
}
|
||||
|
||||
func (l *Logger) FatalField(message string, keyValues ...interface{}) {
|
||||
l.logField(FATAL, message, keyValues...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) PanicField(message string, keyValues ...interface{}) {
|
||||
l.logField(PANIC, message, keyValues...)
|
||||
panic(message)
|
||||
}
|
||||
|
||||
func (l *Logger) Close() error {
|
||||
if f, ok := l.writer.(*os.File); ok {
|
||||
return f.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCaller(skip int) string {
|
||||
_, file, line, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
return "?:?"
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
|
||||
func parseFields(keyValues []interface{}) map[string]interface{} {
|
||||
fields := make(map[string]interface{})
|
||||
for i := 0; i+1 < len(keyValues); i += 2 {
|
||||
key, ok := keyValues[i].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fields[key] = keyValues[i+1]
|
||||
}
|
||||
return fields
|
||||
}
|
||||
Reference in New Issue
Block a user