mediocre-go-lib/mlog/mlog.go

252 lines
6.3 KiB
Go

// Package mlog is a generic logging library. The log methods come in different
// severities: Debug, Info, Warn, Error, and Fatal.
//
// The log methods take in a message string and a Context. The Context can be
// loaded with additional annotations which will be included in the log entry as
// well (see mctx package).
//
package mlog
import (
"context"
"encoding/json"
"io"
"os"
"strings"
"sync"
"github.com/mediocregopher/mediocre-go-lib/mctx"
"github.com/mediocregopher/mediocre-go-lib/merr"
)
// Truncate is a helper function to truncate a string to a given size. It will
// add 3 trailing elipses, so the returned string will be at most size+3
// characters long
func Truncate(s string, size int) string {
if len(s) <= size {
return s
}
return s[:size] + "..."
}
////////////////////////////////////////////////////////////////////////////////
// Level describes the severity of a particular log message, and can be compared
// to the severity of any other Level
type Level interface {
// String gives the string form of the level, e.g. "INFO" or "ERROR"
String() string
// Uint gives an integer indicator of the severity of the level, with zero
// being most severe. If a Level with Uint of zero is logged then the Logger
// implementation provided by this package will exit the process (i.e. zero
// is used as Fatal).
Uint() uint
}
type level struct {
s string
i uint
}
func (l level) String() string {
return l.s
}
func (l level) Uint() uint {
return l.i
}
// All pre-defined log levels
var (
DebugLevel Level = level{s: "DEBUG", i: 40}
InfoLevel Level = level{s: "INFO", i: 30}
WarnLevel Level = level{s: "WARN", i: 20}
ErrorLevel Level = level{s: "ERROR", i: 10}
FatalLevel Level = level{s: "FATAL", i: 0}
)
// LevelFromString takes a string describing one of the pre-defined Levels (e.g.
// "debug" or "INFO") and returns the corresponding Level instance, or nil if
// the string doesn't describe any of the predefined Levels.
func LevelFromString(s string) Level {
switch strings.TrimSpace(strings.ToUpper(s)) {
case "DEBUG":
return DebugLevel
case "INFO":
return InfoLevel
case "WARN":
return WarnLevel
case "ERROR":
return ErrorLevel
case "FATAL":
return FatalLevel
default:
return nil
}
}
////////////////////////////////////////////////////////////////////////////////
// Message describes a message to be logged, after having already resolved the
// KVer
type Message struct {
Level
Description string
Contexts []context.Context
}
// Handler is a function which can process Messages in some way.
//
// NOTE that Logger does not handle thread-safety, that must be done inside the
// Handler if necessary.
type Handler func(msg Message) error
// MessageJSON is the type used to encode Messages to JSON in DefaultHandler
type MessageJSON struct {
Level string `json:"level"`
Description string `json:"descr"`
// path -> key -> value
Annotations map[string]map[string]string `json:"annotations,omitempty"`
}
// DefaultHandler initializes and returns a Handler which will write all
// messages to os.Stderr in a thread-safe way. This is the Handler which
// NewLogger will use automatically.
func DefaultHandler() Handler {
return defaultHandler(os.Stderr)
}
func defaultHandler(out io.Writer) Handler {
l := new(sync.Mutex)
enc := json.NewEncoder(out)
return func(msg Message) error {
l.Lock()
defer l.Unlock()
msgJSON := MessageJSON{
Level: msg.Level.String(),
Description: msg.Description,
}
if len(msg.Contexts) > 0 {
ctx := mctx.MergeAnnotations(msg.Contexts...)
msgJSON.Annotations = mctx.Annotations(ctx).StringMapByPath()
}
return enc.Encode(msgJSON)
}
}
// Logger directs Messages to an internal Handler and provides convenient
// methods for creating and modifying its own behavior. All methods are
// thread-safe.
type Logger struct {
l *sync.RWMutex
h Handler
maxLevel uint
testMsgWrittenCh chan struct{} // only initialized/used in tests
}
// NewLogger initializes and returns a new instance of Logger which will write
// to the DefaultHandler.
func NewLogger() *Logger {
return &Logger{
l: new(sync.RWMutex),
h: DefaultHandler(),
maxLevel: InfoLevel.Uint(),
}
}
// Clone returns an identical instance of the Logger which can be modified
// independently of the original.
func (l *Logger) Clone() *Logger {
l2 := *l
l2.l = new(sync.RWMutex)
return &l2
}
// SetMaxLevel sets the Logger to not log any messages with a higher Level.Uint
// value than of the one given.
func (l *Logger) SetMaxLevel(lvl Level) {
l.l.Lock()
defer l.l.Unlock()
l.maxLevel = lvl.Uint()
}
// SetHandler sets the Logger to use the given Handler in order to process
// Messages.
func (l *Logger) SetHandler(h Handler) {
l.l.Lock()
defer l.l.Unlock()
l.h = h
}
// Handler returns the Handler currently in use by the Logger.
func (l *Logger) Handler() Handler {
l.l.RLock()
defer l.l.RUnlock()
return l.h
}
// Log can be used to manually log a message of some custom defined Level.
//
// If the Level is a fatal (Uint() == 0) then calling this will never return,
// and the process will have os.Exit(1) called.
func (l *Logger) Log(msg Message) {
l.l.RLock()
defer l.l.RUnlock()
if l.maxLevel < msg.Level.Uint() {
return
}
if err := l.h(msg); err != nil {
go l.Error("Logger.Handler returned error", merr.Context(err))
return
}
if l.testMsgWrittenCh != nil {
l.testMsgWrittenCh <- struct{}{}
}
if msg.Level.Uint() == 0 {
os.Exit(1)
}
}
func mkMsg(lvl Level, descr string, ctxs ...context.Context) Message {
return Message{
Level: lvl,
Description: descr,
Contexts: ctxs,
}
}
// Debug logs a DebugLevel message.
func (l *Logger) Debug(descr string, ctxs ...context.Context) {
l.Log(mkMsg(DebugLevel, descr, ctxs...))
}
// Info logs a InfoLevel message.
func (l *Logger) Info(descr string, ctxs ...context.Context) {
l.Log(mkMsg(InfoLevel, descr, ctxs...))
}
// Warn logs a WarnLevel message.
func (l *Logger) Warn(descr string, ctxs ...context.Context) {
l.Log(mkMsg(WarnLevel, descr, ctxs...))
}
// Error logs a ErrorLevel message.
func (l *Logger) Error(descr string, ctxs ...context.Context) {
l.Log(mkMsg(ErrorLevel, descr, ctxs...))
}
// Fatal logs a FatalLevel message. A Fatal message automatically stops the
// process with an os.Exit(1)
func (l *Logger) Fatal(descr string, ctxs ...context.Context) {
l.Log(mkMsg(FatalLevel, descr, ctxs...))
}