2018-01-16 13:59:52 +00:00
|
|
|
// Package mlog is a generic logging library. The log methods come in different
|
|
|
|
// severities: Debug, Info, Warn, Error, and Fatal.
|
|
|
|
//
|
2019-02-09 19:08:30 +00:00
|
|
|
// 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).
|
2018-01-16 13:59:52 +00:00
|
|
|
//
|
|
|
|
package mlog
|
|
|
|
|
|
|
|
import (
|
2019-02-05 20:18:17 +00:00
|
|
|
"context"
|
2019-02-09 19:08:30 +00:00
|
|
|
"encoding/json"
|
2018-01-16 13:59:52 +00:00
|
|
|
"io"
|
|
|
|
"os"
|
2019-02-03 00:27:10 +00:00
|
|
|
"strings"
|
2018-01-16 13:59:52 +00:00
|
|
|
"sync"
|
2019-01-15 04:55:22 +00:00
|
|
|
|
2019-02-05 20:18:17 +00:00
|
|
|
"github.com/mediocregopher/mediocre-go-lib/mctx"
|
2019-01-15 04:55:22 +00:00
|
|
|
"github.com/mediocregopher/mediocre-go-lib/merr"
|
2018-01-16 13:59:52 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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] + "..."
|
|
|
|
}
|
|
|
|
|
2018-11-30 21:27:18 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2018-01-16 13:59:52 +00:00
|
|
|
// 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}
|
|
|
|
)
|
|
|
|
|
2019-02-03 00:27:10 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-30 21:27:18 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
2018-01-16 13:59:52 +00:00
|
|
|
|
|
|
|
// Message describes a message to be logged, after having already resolved the
|
|
|
|
// KVer
|
|
|
|
type Message struct {
|
|
|
|
Level
|
2019-02-05 20:18:17 +00:00
|
|
|
Description string
|
2019-02-09 19:08:30 +00:00
|
|
|
Contexts []context.Context
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2018-11-30 21:27:18 +00:00
|
|
|
// 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
|
|
|
|
|
2019-02-09 19:08:30 +00:00
|
|
|
// 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"`
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2018-11-30 21:27:18 +00:00
|
|
|
// 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 {
|
2019-02-09 19:08:30 +00:00
|
|
|
return defaultHandler(os.Stderr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultHandler(out io.Writer) Handler {
|
2018-11-30 21:27:18 +00:00
|
|
|
l := new(sync.Mutex)
|
2019-02-09 19:08:30 +00:00
|
|
|
enc := json.NewEncoder(out)
|
2018-11-30 21:27:18 +00:00
|
|
|
return func(msg Message) error {
|
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
2019-02-09 19:08:30 +00:00
|
|
|
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()
|
2018-11-30 21:27:18 +00:00
|
|
|
}
|
2019-02-09 19:08:30 +00:00
|
|
|
|
|
|
|
return enc.Encode(msgJSON)
|
2018-11-30 21:27:18 +00:00
|
|
|
}
|
2018-05-28 05:47:23 +00:00
|
|
|
}
|
|
|
|
|
2018-11-30 21:27:18 +00:00
|
|
|
// Logger directs Messages to an internal Handler and provides convenient
|
2019-01-30 21:06:24 +00:00
|
|
|
// methods for creating and modifying its own behavior. All methods are
|
|
|
|
// thread-safe.
|
2018-01-16 13:59:52 +00:00
|
|
|
type Logger struct {
|
2019-01-30 21:06:24 +00:00
|
|
|
l *sync.RWMutex
|
2018-11-30 21:27:18 +00:00
|
|
|
h Handler
|
2018-05-28 05:47:23 +00:00
|
|
|
maxLevel uint
|
2018-01-16 13:59:52 +00:00
|
|
|
|
|
|
|
testMsgWrittenCh chan struct{} // only initialized/used in tests
|
|
|
|
}
|
|
|
|
|
2018-05-28 05:47:23 +00:00
|
|
|
// NewLogger initializes and returns a new instance of Logger which will write
|
2018-11-30 21:27:18 +00:00
|
|
|
// to the DefaultHandler.
|
|
|
|
func NewLogger() *Logger {
|
|
|
|
return &Logger{
|
2019-01-30 21:06:24 +00:00
|
|
|
l: new(sync.RWMutex),
|
2018-11-30 21:27:18 +00:00
|
|
|
h: DefaultHandler(),
|
2018-10-28 19:09:42 +00:00
|
|
|
maxLevel: InfoLevel.Uint(),
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-30 21:06:24 +00:00
|
|
|
// Clone returns an identical instance of the Logger which can be modified
|
|
|
|
// independently of the original.
|
|
|
|
func (l *Logger) Clone() *Logger {
|
2018-11-30 21:27:18 +00:00
|
|
|
l2 := *l
|
2019-01-30 21:06:24 +00:00
|
|
|
l2.l = new(sync.RWMutex)
|
2018-11-30 21:27:18 +00:00
|
|
|
return &l2
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-30 21:06:24 +00:00
|
|
|
// 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()
|
2018-05-28 05:47:23 +00:00
|
|
|
}
|
|
|
|
|
2019-01-30 21:06:24 +00:00
|
|
|
// 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
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-30 21:06:24 +00:00
|
|
|
// 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
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2019-01-15 03:47:16 +00:00
|
|
|
// 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.
|
2018-11-30 23:50:08 +00:00
|
|
|
func (l *Logger) Log(msg Message) {
|
2019-01-30 21:06:24 +00:00
|
|
|
l.l.RLock()
|
|
|
|
defer l.l.RUnlock()
|
|
|
|
|
2018-11-30 23:50:08 +00:00
|
|
|
if l.maxLevel < msg.Level.Uint() {
|
2018-01-16 13:59:52 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-11-30 23:50:08 +00:00
|
|
|
if err := l.h(msg); err != nil {
|
2019-02-09 19:08:30 +00:00
|
|
|
go l.Error("Logger.Handler returned error", merr.Context(err))
|
2018-05-28 05:47:23 +00:00
|
|
|
return
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2018-11-30 21:27:18 +00:00
|
|
|
if l.testMsgWrittenCh != nil {
|
|
|
|
l.testMsgWrittenCh <- struct{}{}
|
2018-10-28 18:10:11 +00:00
|
|
|
}
|
2018-01-16 13:59:52 +00:00
|
|
|
|
2018-11-30 23:50:08 +00:00
|
|
|
if msg.Level.Uint() == 0 {
|
2018-11-30 21:27:18 +00:00
|
|
|
os.Exit(1)
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-09 19:08:30 +00:00
|
|
|
func mkMsg(lvl Level, descr string, ctxs ...context.Context) Message {
|
2018-11-30 23:50:08 +00:00
|
|
|
return Message{
|
|
|
|
Level: lvl,
|
2019-02-05 20:18:17 +00:00
|
|
|
Description: descr,
|
2019-02-09 19:08:30 +00:00
|
|
|
Contexts: ctxs,
|
2018-11-30 23:50:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-09 19:08:30 +00:00
|
|
|
// Debug logs a DebugLevel message.
|
|
|
|
func (l *Logger) Debug(descr string, ctxs ...context.Context) {
|
|
|
|
l.Log(mkMsg(DebugLevel, descr, ctxs...))
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2019-02-09 19:08:30 +00:00
|
|
|
// Info logs a InfoLevel message.
|
|
|
|
func (l *Logger) Info(descr string, ctxs ...context.Context) {
|
|
|
|
l.Log(mkMsg(InfoLevel, descr, ctxs...))
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2019-02-09 19:08:30 +00:00
|
|
|
// Warn logs a WarnLevel message.
|
|
|
|
func (l *Logger) Warn(descr string, ctxs ...context.Context) {
|
|
|
|
l.Log(mkMsg(WarnLevel, descr, ctxs...))
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2019-02-09 19:08:30 +00:00
|
|
|
// Error logs a ErrorLevel message.
|
|
|
|
func (l *Logger) Error(descr string, ctxs ...context.Context) {
|
|
|
|
l.Log(mkMsg(ErrorLevel, descr, ctxs...))
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|
|
|
|
|
2019-02-09 19:08:30 +00:00
|
|
|
// 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...))
|
2018-01-16 13:59:52 +00:00
|
|
|
}
|