pax_global_header00006660000000000000000000000064133653465730014531gustar00rootroot0000000000000052 comment=94f6ae3ed3bceceafa716478c5fbf8d29ca601a1 jwalterweatherman-1.1.0/000077500000000000000000000000001336534657300152545ustar00rootroot00000000000000jwalterweatherman-1.1.0/.gitignore000066400000000000000000000004121336534657300172410ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.bench go.sumjwalterweatherman-1.1.0/LICENSE000066400000000000000000000020671336534657300162660ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Steve Francia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.jwalterweatherman-1.1.0/README.md000066400000000000000000000104521336534657300165350ustar00rootroot00000000000000jWalterWeatherman ================= Seamless printing to the terminal (stdout) and logging to a io.Writer (file) that’s as easy to use as fmt.Println. ![and_that__s_why_you_always_leave_a_note_by_jonnyetc-d57q7um](https://cloud.githubusercontent.com/assets/173412/11002937/ccd01654-847d-11e5-828e-12ebaf582eaf.jpg) Graphic by [JonnyEtc](http://jonnyetc.deviantart.com/art/And-That-s-Why-You-Always-Leave-a-Note-315311422) JWW is primarily a wrapper around the excellent standard log library. It provides a few advantages over using the standard log library alone. 1. Ready to go out of the box. 2. One library for both printing to the terminal and logging (to files). 3. Really easy to log to either a temp file or a file you specify. I really wanted a very straightforward library that could seamlessly do the following things. 1. Replace all the println, printf, etc statements thoughout my code with something more useful 2. Allow the user to easily control what levels are printed to stdout 3. Allow the user to easily control what levels are logged 4. Provide an easy mechanism (like fmt.Println) to print info to the user which can be easily logged as well 5. Due to 2 & 3 provide easy verbose mode for output and logs 6. Not have any unnecessary initialization cruft. Just use it. # Usage ## Step 1. Use it Put calls throughout your source based on type of feedback. No initialization or setup needs to happen. Just start calling things. Available Loggers are: * TRACE * DEBUG * INFO * WARN * ERROR * CRITICAL * FATAL These each are loggers based on the log standard library and follow the standard usage. Eg. ```go import ( jww "github.com/spf13/jwalterweatherman" ) ... if err != nil { // This is a pretty serious error and the user should know about // it. It will be printed to the terminal as well as logged under the // default thresholds. jww.ERROR.Println(err) } if err2 != nil { // This error isn’t going to materially change the behavior of the // application, but it’s something that may not be what the user // expects. Under the default thresholds, Warn will be logged, but // not printed to the terminal. jww.WARN.Println(err2) } // Information that’s relevant to what’s happening, but not very // important for the user. Under the default thresholds this will be // discarded. jww.INFO.Printf("information %q", response) ``` NOTE: You can also use the library in a non-global setting by creating an instance of a Notebook: ```go notepad = jww.NewNotepad(jww.LevelInfo, jww.LevelTrace, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) notepad.WARN.Println("Some warning"") ``` _Why 7 levels?_ Maybe you think that 7 levels are too much for any application... and you are probably correct. Just because there are seven levels doesn’t mean that you should be using all 7 levels. Pick the right set for your needs. Remember they only have to mean something to your project. ## Step 2. Optionally configure JWW Under the default thresholds : * Debug, Trace & Info goto /dev/null * Warn and above is logged (when a log file/io.Writer is provided) * Error and above is printed to the terminal (stdout) ### Changing the thresholds The threshold can be changed at any time, but will only affect calls that execute after the change was made. This is very useful if your application has a verbose mode. Of course you can decide what verbose means to you or even have multiple levels of verbosity. ```go import ( jww "github.com/spf13/jwalterweatherman" ) if Verbose { jww.SetLogThreshold(jww.LevelTrace) jww.SetStdoutThreshold(jww.LevelInfo) } ``` Note that JWW's own internal output uses log levels as well, so set the log level before making any other calls if you want to see what it's up to. ### Setting a log file JWW can log to any `io.Writer`: ```go jww.SetLogOutput(customWriter) ``` # More information This is an early release. I’ve been using it for a while and this is the third interface I’ve tried. I like this one pretty well, but no guarantees that it won’t change a bit. I wrote this for use in [hugo](https://gohugo.io). If you are looking for a static website engine that’s super fast please checkout Hugo. jwalterweatherman-1.1.0/default_notepad.go000066400000000000000000000054101336534657300207410ustar00rootroot00000000000000// Copyright © 2016 Steve Francia . // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package jwalterweatherman import ( "io" "io/ioutil" "log" "os" ) 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 defaultNotepad *Notepad ) func reloadDefaultNotepad() { TRACE = defaultNotepad.TRACE DEBUG = defaultNotepad.DEBUG INFO = defaultNotepad.INFO WARN = defaultNotepad.WARN ERROR = defaultNotepad.ERROR CRITICAL = defaultNotepad.CRITICAL FATAL = defaultNotepad.FATAL LOG = defaultNotepad.LOG FEEDBACK = defaultNotepad.FEEDBACK } func init() { defaultNotepad = NewNotepad(LevelError, LevelWarn, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) reloadDefaultNotepad() } // SetLogThreshold set the log threshold for the default notepad. Trace by default. func SetLogThreshold(threshold Threshold) { defaultNotepad.SetLogThreshold(threshold) reloadDefaultNotepad() } // SetLogOutput set the log output for the default notepad. Discarded by default. func SetLogOutput(handle io.Writer) { defaultNotepad.SetLogOutput(handle) reloadDefaultNotepad() } // SetStdoutThreshold set the standard output threshold for the default notepad. // Info by default. func SetStdoutThreshold(threshold Threshold) { defaultNotepad.SetStdoutThreshold(threshold) reloadDefaultNotepad() } // SetStdoutOutput set the stdout output for the default notepad. Default is stdout. func SetStdoutOutput(handle io.Writer) { defaultNotepad.outHandle = handle defaultNotepad.init() reloadDefaultNotepad() } // SetPrefix set the prefix for the default logger. Empty by default. func SetPrefix(prefix string) { defaultNotepad.SetPrefix(prefix) reloadDefaultNotepad() } // SetFlags set the flags for the default logger. "log.Ldate | log.Ltime" by default. func SetFlags(flags int) { defaultNotepad.SetFlags(flags) reloadDefaultNotepad() } // SetLogListeners configures the default logger with one or more log listeners. func SetLogListeners(l ...LogListener) { defaultNotepad.logListeners = l defaultNotepad.init() reloadDefaultNotepad() } // Level returns the current global log threshold. func LogThreshold() Threshold { return defaultNotepad.logThreshold } // Level returns the current global output threshold. func StdoutThreshold() Threshold { return defaultNotepad.stdoutThreshold } // GetStdoutThreshold returns the defined Treshold for the log logger. func GetLogThreshold() Threshold { return defaultNotepad.GetLogThreshold() } // GetStdoutThreshold returns the Treshold for the stdout logger. func GetStdoutThreshold() Threshold { return defaultNotepad.GetStdoutThreshold() } jwalterweatherman-1.1.0/default_notepad_test.go000066400000000000000000000050761336534657300220100ustar00rootroot00000000000000// Copyright © 2016 Steve Francia . // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package jwalterweatherman import ( "bytes" "io/ioutil" "sync" "testing" "github.com/stretchr/testify/require" ) func TestThresholds(t *testing.T) { SetStdoutThreshold(LevelError) require.Equal(t, StdoutThreshold(), LevelError) SetLogThreshold(LevelCritical) require.Equal(t, LogThreshold(), LevelCritical) require.NotEqual(t, StdoutThreshold(), LevelCritical) SetStdoutThreshold(LevelWarn) require.Equal(t, StdoutThreshold(), LevelWarn) } func TestDefaultLogging(t *testing.T) { var outputBuf, logBuf bytes.Buffer defaultNotepad.logHandle = &logBuf defaultNotepad.outHandle = &outputBuf SetLogThreshold(LevelWarn) SetStdoutThreshold(LevelError) FATAL.Println("fatal err") CRITICAL.Println("critical err") ERROR.Println("an error") WARN.Println("a warning") INFO.Println("information") DEBUG.Println("debugging info") TRACE.Println("trace") require.Contains(t, logBuf.String(), "fatal err") require.Contains(t, logBuf.String(), "critical err") require.Contains(t, logBuf.String(), "an error") require.Contains(t, logBuf.String(), "a warning") require.NotContains(t, logBuf.String(), "information") require.NotContains(t, logBuf.String(), "debugging info") require.NotContains(t, logBuf.String(), "trace") require.Contains(t, outputBuf.String(), "fatal err") require.Contains(t, outputBuf.String(), "critical err") require.Contains(t, outputBuf.String(), "an error") require.NotContains(t, outputBuf.String(), "a warning") require.NotContains(t, outputBuf.String(), "information") require.NotContains(t, outputBuf.String(), "debugging info") require.NotContains(t, outputBuf.String(), "trace") } func TestLogCounter(t *testing.T) { assert := require.New(t) defaultNotepad.logHandle = ioutil.Discard defaultNotepad.outHandle = ioutil.Discard errorCounter := &Counter{} SetLogThreshold(LevelTrace) SetStdoutThreshold(LevelTrace) SetLogListeners(LogCounter(errorCounter, LevelError)) FATAL.Println("fatal err") CRITICAL.Println("critical err") WARN.Println("a warning") WARN.Println("another warning") INFO.Println("information") DEBUG.Println("debugging info") TRACE.Println("trace") wg := &sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() for j := 0; j < 10; j++ { ERROR.Println("error", j) // check for data races assert.True(errorCounter.Count() > uint64(j)) } }() } wg.Wait() assert.Equal(uint64(102), errorCounter.Count()) } jwalterweatherman-1.1.0/go.mod000066400000000000000000000002741336534657300163650ustar00rootroot00000000000000module github.com/spf13/jwalterweatherman require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 ) jwalterweatherman-1.1.0/log_counter.go000066400000000000000000000016761336534657300201350ustar00rootroot00000000000000// Copyright © 2016 Steve Francia . // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package jwalterweatherman import ( "io" "sync/atomic" ) // Counter is an io.Writer that increments a counter on Write. type Counter struct { count uint64 } func (c *Counter) incr() { atomic.AddUint64(&c.count, 1) } // Reset resets the counter. func (c *Counter) Reset() { atomic.StoreUint64(&c.count, 0) } // Count returns the current count. func (c *Counter) Count() uint64 { return atomic.LoadUint64(&c.count) } func (c *Counter) Write(p []byte) (n int, err error) { c.incr() return len(p), nil } // LogCounter creates a LogListener that counts log statements >= the given threshold. func LogCounter(counter *Counter, t1 Threshold) LogListener { return func(t2 Threshold) io.Writer { if t2 < t1 { // Not interested in this threshold. return nil } return counter } } jwalterweatherman-1.1.0/notepad.go000066400000000000000000000125521336534657300172420ustar00rootroot00000000000000// Copyright © 2016 Steve Francia . // // 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" ) type Threshold int func (t Threshold) String() string { return prefixes[t] } const ( LevelTrace Threshold = iota LevelDebug LevelInfo LevelWarn LevelError LevelCritical LevelFatal ) var prefixes map[Threshold]string = map[Threshold]string{ LevelTrace: "TRACE", LevelDebug: "DEBUG", LevelInfo: "INFO", LevelWarn: "WARN", LevelError: "ERROR", LevelCritical: "CRITICAL", LevelFatal: "FATAL", } // Notepad is where you leave a note! type Notepad struct { 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 loggers [7]**log.Logger logHandle io.Writer outHandle io.Writer logThreshold Threshold stdoutThreshold Threshold prefix string flags int logListeners []LogListener } // A LogListener can ble supplied to a Notepad to listen on log writes for a given // threshold. This can be used to capture log events in unit tests and similar. // Note that this function will be invoked once for each log threshold. If // the given threshold is not of interest to you, return nil. // Note that these listeners will receive log events for a given threshold, even // if the current configuration says not to log it. That way you can count ERRORs even // if you don't print them to the console. type LogListener func(t Threshold) io.Writer // NewNotepad creates a new Notepad. func NewNotepad( outThreshold Threshold, logThreshold Threshold, outHandle, logHandle io.Writer, prefix string, flags int, logListeners ...LogListener, ) *Notepad { n := &Notepad{logListeners: logListeners} n.loggers = [7]**log.Logger{&n.TRACE, &n.DEBUG, &n.INFO, &n.WARN, &n.ERROR, &n.CRITICAL, &n.FATAL} n.outHandle = outHandle n.logHandle = logHandle n.stdoutThreshold = outThreshold n.logThreshold = logThreshold if len(prefix) != 0 { n.prefix = "[" + prefix + "] " } else { n.prefix = "" } n.flags = flags n.LOG = log.New(n.logHandle, "LOG: ", n.flags) n.FEEDBACK = &Feedback{out: log.New(outHandle, "", 0), log: n.LOG} n.init() return n } // init creates the loggers for each level depending on the notepad thresholds. func (n *Notepad) init() { logAndOut := io.MultiWriter(n.outHandle, n.logHandle) for t, logger := range n.loggers { threshold := Threshold(t) prefix := n.prefix + threshold.String() + " " switch { case threshold >= n.logThreshold && threshold >= n.stdoutThreshold: *logger = log.New(n.createLogWriters(threshold, logAndOut), prefix, n.flags) case threshold >= n.logThreshold: *logger = log.New(n.createLogWriters(threshold, n.logHandle), prefix, n.flags) case threshold >= n.stdoutThreshold: *logger = log.New(n.createLogWriters(threshold, n.outHandle), prefix, n.flags) default: *logger = log.New(n.createLogWriters(threshold, ioutil.Discard), prefix, n.flags) } } } func (n *Notepad) createLogWriters(t Threshold, handle io.Writer) io.Writer { if len(n.logListeners) == 0 { return handle } writers := []io.Writer{handle} for _, l := range n.logListeners { w := l(t) if w != nil { writers = append(writers, w) } } if len(writers) == 1 { return handle } return io.MultiWriter(writers...) } // SetLogThreshold changes the threshold above which messages are written to the // log file. func (n *Notepad) SetLogThreshold(threshold Threshold) { n.logThreshold = threshold n.init() } // SetLogOutput changes the file where log messages are written. func (n *Notepad) SetLogOutput(handle io.Writer) { n.logHandle = handle n.init() } // GetStdoutThreshold returns the defined Treshold for the log logger. func (n *Notepad) GetLogThreshold() Threshold { return n.logThreshold } // SetStdoutThreshold changes the threshold above which messages are written to the // standard output. func (n *Notepad) SetStdoutThreshold(threshold Threshold) { n.stdoutThreshold = threshold n.init() } // GetStdoutThreshold returns the Treshold for the stdout logger. func (n *Notepad) GetStdoutThreshold() Threshold { return n.stdoutThreshold } // SetPrefix changes the prefix used by the notepad. Prefixes are displayed between // brackets at the beginning of the line. An empty prefix won't be displayed at all. func (n *Notepad) SetPrefix(prefix string) { if len(prefix) != 0 { n.prefix = "[" + prefix + "] " } else { n.prefix = "" } n.init() } // SetFlags choose which flags the logger will display (after prefix and message // level). See the package log for more informations on this. func (n *Notepad) SetFlags(flags int) { n.flags = flags n.init() } // Feedback writes plainly to the outHandle while // logging with the standard extra information (date, file, etc). type Feedback struct { out *log.Logger log *log.Logger } func (fb *Feedback) Println(v ...interface{}) { fb.output(fmt.Sprintln(v...)) } func (fb *Feedback) Printf(format string, v ...interface{}) { fb.output(fmt.Sprintf(format, v...)) } func (fb *Feedback) Print(v ...interface{}) { fb.output(fmt.Sprint(v...)) } func (fb *Feedback) output(s string) { if fb.out != nil { fb.out.Output(2, s) } if fb.log != nil { fb.log.Output(2, s) } } jwalterweatherman-1.1.0/notepad_test.go000066400000000000000000000044551336534657300203040ustar00rootroot00000000000000// Copyright © 2016 Steve Francia . // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package jwalterweatherman import ( "bytes" "io" "io/ioutil" "testing" "github.com/stretchr/testify/require" ) func TestNotepad(t *testing.T) { var logHandle, outHandle bytes.Buffer errorCounter := &Counter{} n := NewNotepad(LevelCritical, LevelError, &outHandle, &logHandle, "TestNotePad", 0, LogCounter(errorCounter, LevelError)) require.Equal(t, LevelCritical, n.GetStdoutThreshold()) require.Equal(t, LevelError, n.GetLogThreshold()) n.DEBUG.Println("Some debug") n.ERROR.Println("Some error") n.CRITICAL.Println("Some critical error") require.Contains(t, logHandle.String(), "[TestNotePad] ERROR Some error") require.NotContains(t, logHandle.String(), "Some debug") require.NotContains(t, outHandle.String(), "Some error") require.Contains(t, outHandle.String(), "Some critical error") // 1 error + 1 critical require.Equal(t, errorCounter.Count(), uint64(2)) } func TestNotepadLogListener(t *testing.T) { assert := require.New(t) var errorBuff, infoBuff bytes.Buffer errorCapture := func(t Threshold) io.Writer { if t != LevelError { // Only interested in ERROR return nil } return &errorBuff } infoCapture := func(t Threshold) io.Writer { if t != LevelInfo { return nil } return &infoBuff } n := NewNotepad(LevelCritical, LevelError, ioutil.Discard, ioutil.Discard, "TestNotePad", 0, infoCapture, errorCapture) n.DEBUG.Println("Some debug") n.INFO.Println("Some info") n.INFO.Println("Some more info") n.ERROR.Println("Some error") n.CRITICAL.Println("Some critical error") n.ERROR.Println("Some more error") assert.Equal(`[TestNotePad] ERROR Some error [TestNotePad] ERROR Some more error `, errorBuff.String()) assert.Equal(`[TestNotePad] INFO Some info [TestNotePad] INFO Some more info `, infoBuff.String()) } func TestThresholdString(t *testing.T) { require.Equal(t, LevelError.String(), "ERROR") require.Equal(t, LevelTrace.String(), "TRACE") } func BenchmarkLogPrintOnlyToCounter(b *testing.B) { var logHandle, outHandle bytes.Buffer n := NewNotepad(LevelCritical, LevelCritical, &outHandle, &logHandle, "TestNotePad", 0) b.ResetTimer() for i := 0; i < b.N; i++ { n.INFO.Print("Test") } }