Skip to content

Commit

Permalink
Provide a way to filter out log messages easily (#64)
Browse files Browse the repository at this point in the history
Co-authored-by: Jim Kalafut <jkalafut@hashicorp.com>
Co-authored-by: Dan Slimmon <dslimmon@hashicorp.com>
Co-authored-by: Clint <catsby@users.noreply.github.com>
  • Loading branch information
4 people authored May 6, 2020
1 parent 24cddb1 commit ec1a562
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 0 deletions.
71 changes: 71 additions & 0 deletions exclude.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package hclog

import (
"regexp"
"strings"
)

// ExcludeByMessage provides a simple way to build a list of log messages that
// can be queried and matched. This is meant to be used with the Exclude
// option on Options to suppress log messages. This does not hold any mutexs
// within itself, so normal usage would be to Add entries at setup and none after
// Exclude is going to be called. Exclude is called with a mutex held within
// the Logger, so that doesn't need to use a mutex. Example usage:
//
// f := new(ExcludeByMessage)
// f.Add("Noisy log message text")
// appLogger.Exclude = f.Exclude
type ExcludeByMessage struct {
messages map[string]struct{}
}

// Add a message to be filtered. Do not call this after Exclude is to be called
// due to concurrency issues.
func (f *ExcludeByMessage) Add(msg string) {
if f.messages == nil {
f.messages = make(map[string]struct{})
}

f.messages[msg] = struct{}{}
}

// Return true if the given message should be included
func (f *ExcludeByMessage) Exclude(level Level, msg string, args ...interface{}) bool {
_, ok := f.messages[msg]
return ok
}

// ExcludeByPrefix is a simple type to match a message string that has a common prefix.
type ExcludeByPrefix string

// Matches an message that starts with the prefix.
func (p ExcludeByPrefix) Exclude(level Level, msg string, args ...interface{}) bool {
return strings.HasPrefix(msg, string(p))
}

// ExcludeByRegexp takes a regexp and uses it to match a log message string. If it matches
// the log entry is excluded.
type ExcludeByRegexp struct {
Regexp *regexp.Regexp
}

// Exclude the log message if the message string matches the regexp
func (e ExcludeByRegexp) Exclude(level Level, msg string, args ...interface{}) bool {
return e.Regexp.MatchString(msg)
}

// ExcludeFuncs is a slice of functions that will called to see if a log entry
// should be filtered or not. It stops calling functions once at least one returns
// true.
type ExcludeFuncs []func(level Level, msg string, args ...interface{}) bool

// Calls each function until one of them returns true
func (ff ExcludeFuncs) Exclude(level Level, msg string, args ...interface{}) bool {
for _, f := range ff {
if f(level, msg, args...) {
return true
}
}

return false
}
55 changes: 55 additions & 0 deletions exclude_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package hclog

import (
"regexp"
"testing"

"github.com/stretchr/testify/assert"
)

func TestExclude(t *testing.T) {
t.Run("excludes by message", func(t *testing.T) {
var em ExcludeByMessage
em.Add("foo")
em.Add("bar")

assert.True(t, em.Exclude(Info, "foo"))
assert.True(t, em.Exclude(Info, "bar"))
assert.False(t, em.Exclude(Info, "qux"))
assert.False(t, em.Exclude(Info, "foo qux"))
assert.False(t, em.Exclude(Info, "qux bar"))
})

t.Run("excludes by prefix", func(t *testing.T) {
ebp := ExcludeByPrefix("foo: ")

assert.True(t, ebp.Exclude(Info, "foo: rocks"))
assert.False(t, ebp.Exclude(Info, "foo"))
assert.False(t, ebp.Exclude(Info, "qux foo: bar"))
})

t.Run("exclude by regexp", func(t *testing.T) {
ebr := &ExcludeByRegexp{
Regexp: regexp.MustCompile("(foo|bar)"),
}

assert.True(t, ebr.Exclude(Info, "foo"))
assert.True(t, ebr.Exclude(Info, "bar"))
assert.True(t, ebr.Exclude(Info, "foo qux"))
assert.True(t, ebr.Exclude(Info, "qux bar"))
assert.False(t, ebr.Exclude(Info, "qux"))
})

t.Run("excludes many funcs", func(t *testing.T) {
ef := ExcludeFuncs{
ExcludeByPrefix("foo: ").Exclude,
ExcludeByPrefix("bar: ").Exclude,
}

assert.True(t, ef.Exclude(Info, "foo: rocks"))
assert.True(t, ef.Exclude(Info, "bar: rocks"))
assert.False(t, ef.Exclude(Info, "foo"))
assert.False(t, ef.Exclude(Info, "qux foo: bar"))

})
}
7 changes: 7 additions & 0 deletions intlogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ type intLogger struct {
level *int32

implied []interface{}

exclude func(level Level, msg string, args ...interface{}) bool
}

// New returns a configured logger.
Expand Down Expand Up @@ -106,6 +108,7 @@ func newLogger(opts *LoggerOptions) *intLogger {
mutex: mutex,
writer: newWriter(output, opts.Color),
level: new(int32),
exclude: opts.Exclude,
}

l.setColorization(opts)
Expand All @@ -131,6 +134,10 @@ func (l *intLogger) log(name string, level Level, msg string, args ...interface{
l.mutex.Lock()
defer l.mutex.Unlock()

if l.exclude != nil && l.exclude(level, msg, args...) {
return
}

if l.json {
l.logJSON(t, name, level, msg, args...)
} else {
Expand Down
6 changes: 6 additions & 0 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ type LoggerOptions struct {
// Color the output. On Windows, colored logs are only avaiable for io.Writers that
// are concretely instances of *os.File.
Color ColorOption

// A function which is called with the log information and if it returns true the value
// should not be logged.
// This is useful when interacting with a system that you wish to suppress the log
// message for (because it's too noisy, etc)
Exclude func(level Level, msg string, args ...interface{}) bool
}

// InterceptLogger describes the interface for using a logger
Expand Down

0 comments on commit ec1a562

Please sign in to comment.