package logger import ( "bytes" "encoding/json" "fmt" "sync" "time" "github.com/fatih/color" "github.com/pkg/errors" ) type Level int func (l Level) MarshalJSON() ([]byte, error) { return json.Marshal(l.String()) } func (l *Level) UnmarshalJSON(input []byte) (err error) { var s string if err = json.Unmarshal(input, &s); err != nil { return err } *l, err = ParseLevel(s) return err } // implement flag.Value // implement github.com/spf13/pflag.Value func (l *Level) Set(s string) error { newl, err := ParseLevel(s) if err != nil { return err } *l = newl return nil } // implement github.com/spf13/pflag.Value func (l *Level) Type() string { var buf bytes.Buffer for i, l := range AllLevels { fmt.Fprintf(&buf, "%s", l) if i != len(AllLevels)-1 { fmt.Fprintf(&buf, "|") } } return fmt.Sprintf("(%s)", buf.String()) } const ( Debug Level = iota Info Warn Error ) func (l Level) Short() string { switch l { case Debug: return "DEBG" case Info: return "INFO" case Warn: return "WARN" case Error: return "ERRO" default: return l.String() } } func (l Level) String() string { switch l { case Debug: return "debug" case Info: return "info" case Warn: return "warn" case Error: return "error" default: return fmt.Sprintf("unknown level %d", l) } } func ParseLevel(s string) (l Level, err error) { for _, l := range AllLevels { if s == l.String() { return l, nil } } return -1, errors.Errorf("unknown level '%s'", s) } // Levels ordered least severe to most severe var AllLevels []Level = []Level{Debug, Info, Warn, Error} type Fields map[string]interface{} type Entry struct { Level Level Message string Time time.Time Fields Fields } func (e Entry) Color() *color.Color { c := color.New() switch e.Level { case Debug: c.Add(color.FgHiBlue) case Info: c.Add(color.FgHiGreen) case Warn: c.Add(color.FgHiYellow) case Error: c.Add(color.FgHiRed) } return c } // An outlet receives log entries produced by the Logger and writes them to some destination. type Outlet interface { // Write the entry to the destination. // // Logger waits for all outlets to return from WriteEntry() before returning from the log call. // An implementation of Outlet must assert that it does not block in WriteEntry. // Otherwise, it will slow down the program. // // Note: os.Stderr is also used by logger.Logger for reporting errors returned by outlets // => you probably don't want to log there WriteEntry(entry Entry) error } type Outlets struct { mtx sync.RWMutex outs map[Level][]Outlet } func NewOutlets() *Outlets { return &Outlets{ mtx: sync.RWMutex{}, outs: make(map[Level][]Outlet, len(AllLevels)), } } func (os *Outlets) DeepCopy() (copy *Outlets) { copy = NewOutlets() for level := range os.outs { for i := range os.outs[level] { copy.outs[level] = append(copy.outs[level], os.outs[level][i]) } } return copy } func (os *Outlets) Add(outlet Outlet, minLevel Level) { os.mtx.Lock() defer os.mtx.Unlock() for _, l := range AllLevels[minLevel:] { os.outs[l] = append(os.outs[l], outlet) } } func (os *Outlets) Get(level Level) []Outlet { os.mtx.RLock() defer os.mtx.RUnlock() return os.outs[level] } // Return the first outlet added to this Outlets list using Add() // with minLevel <= Error. // If no such outlet is in this Outlets list, a discarding outlet is returned. func (os *Outlets) GetLoggerErrorOutlet() Outlet { os.mtx.RLock() defer os.mtx.RUnlock() if len(os.outs[Error]) < 1 { return nullOutlet{} } return os.outs[Error][0] } type nullOutlet struct{} func (nullOutlet) WriteEntry(entry Entry) error { return nil }