| // Copyright © 2016 Steve Francia <spf@spf13.com>. |
| // |
| // Use of this source code is governed by an MIT-style |
| // license that can be found in the LICENSE file. |
| |
| package jwalterweatherman |
| |
| import ( |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "os" |
| "sync/atomic" |
| ) |
| |
| // Level describes the chosen log level between |
| // debug and critical. |
| type Level int |
| |
| type NotePad struct { |
| Handle io.Writer |
| Level Level |
| Prefix string |
| Logger **log.Logger |
| counter uint64 |
| } |
| |
| func (n *NotePad) incr() { |
| atomic.AddUint64(&n.counter, 1) |
| } |
| |
| func (n *NotePad) resetCounter() { |
| atomic.StoreUint64(&n.counter, 0) |
| } |
| |
| func (n *NotePad) getCount() uint64 { |
| return atomic.LoadUint64(&n.counter) |
| } |
| |
| type countingWriter struct { |
| incrFunc func() |
| } |
| |
| func (cw *countingWriter) Write(p []byte) (n int, err error) { |
| cw.incrFunc() |
| |
| return 0, nil |
| } |
| |
| // Feedback is special. It writes plainly to the output while |
| // logging with the standard extra information (date, file, etc) |
| // Only Println and Printf are currently provided for this |
| type Feedback struct{} |
| |
| const ( |
| LevelTrace Level = iota |
| LevelDebug |
| LevelInfo |
| LevelWarn |
| LevelError |
| LevelCritical |
| LevelFatal |
| DefaultLogThreshold = LevelWarn |
| DefaultStdoutThreshold = LevelError |
| ) |
| |
| var ( |
| TRACE *log.Logger |
| DEBUG *log.Logger |
| INFO *log.Logger |
| WARN *log.Logger |
| ERROR *log.Logger |
| CRITICAL *log.Logger |
| FATAL *log.Logger |
| LOG *log.Logger |
| FEEDBACK Feedback |
| LogHandle io.Writer = ioutil.Discard |
| OutHandle io.Writer = os.Stdout |
| BothHandle io.Writer = io.MultiWriter(LogHandle, OutHandle) |
| NotePads []*NotePad = []*NotePad{trace, debug, info, warn, err, critical, fatal} |
| |
| trace *NotePad = &NotePad{Level: LevelTrace, Handle: os.Stdout, Logger: &TRACE, Prefix: "TRACE: "} |
| debug *NotePad = &NotePad{Level: LevelDebug, Handle: os.Stdout, Logger: &DEBUG, Prefix: "DEBUG: "} |
| info *NotePad = &NotePad{Level: LevelInfo, Handle: os.Stdout, Logger: &INFO, Prefix: "INFO: "} |
| warn *NotePad = &NotePad{Level: LevelWarn, Handle: os.Stdout, Logger: &WARN, Prefix: "WARN: "} |
| err *NotePad = &NotePad{Level: LevelError, Handle: os.Stdout, Logger: &ERROR, Prefix: "ERROR: "} |
| critical *NotePad = &NotePad{Level: LevelCritical, Handle: os.Stdout, Logger: &CRITICAL, Prefix: "CRITICAL: "} |
| fatal *NotePad = &NotePad{Level: LevelFatal, Handle: os.Stdout, Logger: &FATAL, Prefix: "FATAL: "} |
| logThreshold Level = DefaultLogThreshold |
| outputThreshold Level = DefaultStdoutThreshold |
| ) |
| |
| const ( |
| DATE = log.Ldate |
| TIME = log.Ltime |
| SFILE = log.Lshortfile |
| LFILE = log.Llongfile |
| MSEC = log.Lmicroseconds |
| ) |
| |
| var logFlags = DATE | TIME | SFILE |
| |
| func init() { |
| SetStdoutThreshold(DefaultStdoutThreshold) |
| } |
| |
| // initialize will setup the jWalterWeatherman standard approach of providing the user |
| // some feedback and logging a potentially different amount based on independent log and output thresholds. |
| // By default the output has a lower threshold than logged |
| // Don't use if you have manually set the Handles of the different levels as it will overwrite them. |
| func initialize() { |
| BothHandle = io.MultiWriter(LogHandle, OutHandle) |
| |
| for _, n := range NotePads { |
| if n.Level < outputThreshold && n.Level < logThreshold { |
| n.Handle = ioutil.Discard |
| } else if n.Level >= outputThreshold && n.Level >= logThreshold { |
| n.Handle = BothHandle |
| } else if n.Level >= outputThreshold && n.Level < logThreshold { |
| n.Handle = OutHandle |
| } else { |
| n.Handle = LogHandle |
| } |
| } |
| |
| for _, n := range NotePads { |
| n.Handle = io.MultiWriter(n.Handle, &countingWriter{n.incr}) |
| *n.Logger = log.New(n.Handle, n.Prefix, logFlags) |
| } |
| |
| LOG = log.New(LogHandle, |
| "LOG: ", |
| logFlags) |
| } |
| |
| // Set the log Flags (Available flag: DATE, TIME, SFILE, LFILE and MSEC) |
| func SetLogFlag(flags int) { |
| logFlags = flags |
| initialize() |
| } |
| |
| // Level returns the current global log threshold. |
| func LogThreshold() Level { |
| return logThreshold |
| } |
| |
| // Level returns the current global output threshold. |
| func StdoutThreshold() Level { |
| return outputThreshold |
| } |
| |
| // Ensures that the level provided is within the bounds of available levels |
| func levelCheck(level Level) Level { |
| switch { |
| case level <= LevelTrace: |
| return LevelTrace |
| case level >= LevelFatal: |
| return LevelFatal |
| default: |
| return level |
| } |
| } |
| |
| // Establishes a threshold where anything matching or above will be logged |
| func SetLogThreshold(level Level) { |
| logThreshold = levelCheck(level) |
| initialize() |
| } |
| |
| // Establishes a threshold where anything matching or above will be output |
| func SetStdoutThreshold(level Level) { |
| outputThreshold = levelCheck(level) |
| initialize() |
| } |
| |
| // Conveniently Sets the Log Handle to a io.writer created for the file behind the given filepath |
| // Will only append to this file |
| func SetLogFile(path string) { |
| file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) |
| if err != nil { |
| CRITICAL.Println("Failed to open log file:", path, err) |
| os.Exit(-1) |
| } |
| |
| INFO.Println("Logging to", file.Name()) |
| |
| LogHandle = file |
| initialize() |
| } |
| |
| // Conveniently Creates a temporary file and sets the Log Handle to a io.writer created for it |
| func UseTempLogFile(prefix string) { |
| file, err := ioutil.TempFile(os.TempDir(), prefix) |
| if err != nil { |
| CRITICAL.Println(err) |
| } |
| |
| INFO.Println("Logging to", file.Name()) |
| |
| LogHandle = file |
| initialize() |
| } |
| |
| // LogCountForLevel returns the number of log invocations for a given level. |
| func LogCountForLevel(l Level) uint64 { |
| for _, np := range NotePads { |
| if np.Level == l { |
| return np.getCount() |
| } |
| } |
| return 0 |
| } |
| |
| // LogCountForLevelsGreaterThanorEqualTo returns the number of log invocations |
| // greater than or equal to a given level threshold. |
| func LogCountForLevelsGreaterThanorEqualTo(threshold Level) uint64 { |
| var cnt uint64 |
| for _, np := range NotePads { |
| if np.Level >= threshold { |
| cnt += np.getCount() |
| } |
| } |
| return cnt |
| } |
| |
| // ResetLogCounters resets the invocation counters for all levels. |
| func ResetLogCounters() { |
| for _, np := range NotePads { |
| np.resetCounter() |
| } |
| } |
| |
| // Disables logging for the entire JWW system |
| func DiscardLogging() { |
| LogHandle = ioutil.Discard |
| initialize() |
| } |
| |
| // Feedback is special. It writes plainly to the output while |
| // logging with the standard extra information (date, file, etc) |
| // Only Println and Printf are currently provided for this |
| func (fb *Feedback) Println(v ...interface{}) { |
| s := fmt.Sprintln(v...) |
| fmt.Print(s) |
| LOG.Output(2, s) |
| } |
| |
| // Feedback is special. It writes plainly to the output while |
| // logging with the standard extra information (date, file, etc) |
| // Only Println and Printf are currently provided for this |
| func (fb *Feedback) Printf(format string, v ...interface{}) { |
| s := fmt.Sprintf(format, v...) |
| fmt.Print(s) |
| LOG.Output(2, s) |
| } |