Add log counters

The use case in Hugo is to exit with an error code
when *any* ERROR logging has occured.

See https://github.com/spf13/hugo/issues/740
diff --git a/jww_test.go b/jww_test.go
index b6d118a..c405185 100644
--- a/jww_test.go
+++ b/jww_test.go
@@ -1,4 +1,4 @@
-// Copyright © 2014 Steve Francia <spf@spf13.com>.
+// 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.
@@ -6,51 +6,91 @@
 package jwalterweatherman
 
 import (
-    "bytes"
-    "github.com/stretchr/testify/assert"
-    "testing"
+	"bytes"
+	"github.com/stretchr/testify/assert"
+	"sync"
+	"testing"
 )
 
 func TestLevels(t *testing.T) {
-    SetStdoutThreshold(LevelError)
-    assert.Equal(t, StdoutThreshold(), LevelError)
-    SetLogThreshold(LevelCritical)
-    assert.Equal(t, LogThreshold(), LevelCritical)
-    assert.NotEqual(t, StdoutThreshold(), LevelCritical)
-    SetStdoutThreshold(LevelWarn)
-    assert.Equal(t, StdoutThreshold(), LevelWarn)
+	SetStdoutThreshold(LevelError)
+	assert.Equal(t, StdoutThreshold(), LevelError)
+	SetLogThreshold(LevelCritical)
+	assert.Equal(t, LogThreshold(), LevelCritical)
+	assert.NotEqual(t, StdoutThreshold(), LevelCritical)
+	SetStdoutThreshold(LevelWarn)
+	assert.Equal(t, StdoutThreshold(), LevelWarn)
 }
 
 func TestDefaultLogging(t *testing.T) {
-    outputBuf := new(bytes.Buffer)
-    logBuf := new(bytes.Buffer)
-    LogHandle = logBuf
-    OutHandle = outputBuf
+	outputBuf := new(bytes.Buffer)
+	logBuf := new(bytes.Buffer)
+	LogHandle = logBuf
+	OutHandle = outputBuf
 
-    SetLogThreshold(LevelWarn)
-    SetStdoutThreshold(LevelError)
+	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")
+	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")
 
-    assert.Contains(t, logBuf.String(), "fatal err")
-    assert.Contains(t, logBuf.String(), "critical err")
-    assert.Contains(t, logBuf.String(), "an error")
-    assert.Contains(t, logBuf.String(), "a warning")
-    assert.NotContains(t, logBuf.String(), "information")
-    assert.NotContains(t, logBuf.String(), "debugging info")
-    assert.NotContains(t, logBuf.String(), "trace")
+	assert.Contains(t, logBuf.String(), "fatal err")
+	assert.Contains(t, logBuf.String(), "critical err")
+	assert.Contains(t, logBuf.String(), "an error")
+	assert.Contains(t, logBuf.String(), "a warning")
+	assert.NotContains(t, logBuf.String(), "information")
+	assert.NotContains(t, logBuf.String(), "debugging info")
+	assert.NotContains(t, logBuf.String(), "trace")
 
-    assert.Contains(t, outputBuf.String(), "fatal err")
-    assert.Contains(t, outputBuf.String(), "critical err")
-    assert.Contains(t, outputBuf.String(), "an error")
-    assert.NotContains(t, outputBuf.String(), "a warning")
-    assert.NotContains(t, outputBuf.String(), "information")
-    assert.NotContains(t, outputBuf.String(), "debugging info")
-    assert.NotContains(t, outputBuf.String(), "trace")
+	assert.Contains(t, outputBuf.String(), "fatal err")
+	assert.Contains(t, outputBuf.String(), "critical err")
+	assert.Contains(t, outputBuf.String(), "an error")
+	assert.NotContains(t, outputBuf.String(), "a warning")
+	assert.NotContains(t, outputBuf.String(), "information")
+	assert.NotContains(t, outputBuf.String(), "debugging info")
+	assert.NotContains(t, outputBuf.String(), "trace")
+}
+
+func TestLogCounter(t *testing.T) {
+	ResetLogCounters()
+
+	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(t, LogCountForLevel(LevelError) > uint64(j))
+				assert.True(t, LogCountForLevelsGreaterThanorEqualTo(LevelError) > uint64(j))
+			}
+		}()
+
+	}
+
+	wg.Wait()
+
+	assert.Equal(t, uint64(1), LogCountForLevel(LevelFatal))
+	assert.Equal(t, uint64(1), LogCountForLevel(LevelCritical))
+	assert.Equal(t, uint64(2), LogCountForLevel(LevelWarn))
+	assert.Equal(t, uint64(1), LogCountForLevel(LevelInfo))
+	assert.Equal(t, uint64(1), LogCountForLevel(LevelDebug))
+	assert.Equal(t, uint64(1), LogCountForLevel(LevelTrace))
+	assert.Equal(t, uint64(100), LogCountForLevel(LevelError))
+	assert.Equal(t, uint64(102), LogCountForLevelsGreaterThanorEqualTo(LevelError))
 }
diff --git a/thatswhyyoualwaysleaveanote.go b/thatswhyyoualwaysleaveanote.go
index 30f6d54..df5970c 100644
--- a/thatswhyyoualwaysleaveanote.go
+++ b/thatswhyyoualwaysleaveanote.go
@@ -1,4 +1,4 @@
-// Copyright © 2014 Steve Francia <spf@spf13.com>.
+// 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.
@@ -11,6 +11,7 @@
 	"io/ioutil"
 	"log"
 	"os"
+	"sync/atomic"
 )
 
 // Level describes the chosen log level between
@@ -18,10 +19,33 @@
 type Level int
 
 type NotePad struct {
-	Handle io.Writer
-	Level  Level
-	Prefix string
-	Logger **log.Logger
+	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
@@ -101,6 +125,7 @@
 	}
 
 	for _, n := range NotePads {
+		n.Handle = io.MultiWriter(n.Handle, &countingWriter{n.incr})
 		*n.Logger = log.New(n.Handle, n.Prefix, logFlags)
 	}
 
@@ -176,6 +201,35 @@
 	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