Skip to content

Commit

Permalink
Merge pull request #17 from kevinconway/pool-opt-out
Browse files Browse the repository at this point in the history
Add option to opt-out of sync.Pool use
  • Loading branch information
rs authored Aug 13, 2017
2 parents 6d90616 + fcb2e1b commit 7ed5dc1
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 14 deletions.
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
language: go
go:
- 1.5
- 1.6
- 1.7
- 1.8
- tip
Expand Down
39 changes: 27 additions & 12 deletions xlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,21 @@ type Config struct {
// You should always wrap your output with an OutputChannel otherwise your
// logger will be connected to its output synchronously.
Output Output
// DisablePooling removes the use of a sync.Pool for cases where logger
// instances are needed beyond the scope of a request handler. This option
// puts a greater pressure on GC and increases the amount of memory allocated
// and freed. Use only if persistent loggers are a requirement.
DisablePooling bool
}

// F represents a set of log message fields
type F map[string]interface{}

type logger struct {
level Level
output Output
fields F
level Level
output Output
fields F
disablePooling bool
}

// Common field names for log messages.
Expand All @@ -121,7 +127,7 @@ var exit1 = func() { os.Exit(1) }
// critialLogger is a logger to use when xlog is not able to deliver a message
var critialLogger = log.New(os.Stderr, "xlog: ", log.Ldate|log.Ltime|log.LUTC|log.Lshortfile)

var loggerPool = sync.Pool{
var loggerPool = &sync.Pool{
New: func() interface{} {
return &logger{}
},
Expand All @@ -131,7 +137,12 @@ var loggerPool = sync.Pool{
//
// This function should only be used out of a request. Use FromContext in request.
func New(c Config) Logger {
l := loggerPool.Get().(*logger)
var l *logger
if c.DisablePooling {
l = &logger{}
} else {
l = loggerPool.Get().(*logger)
}
l.level = c.Level
l.output = c.Output
if l.output == nil {
Expand All @@ -140,6 +151,7 @@ func New(c Config) Logger {
for k, v := range c.Fields {
l.SetField(k, v)
}
l.disablePooling = c.DisablePooling
return l
}

Expand All @@ -155,9 +167,10 @@ func Copy(l Logger) Logger {
// Copy returns a copy of the logger
func (l *logger) Copy() Logger {
l2 := &logger{
level: l.level,
output: l.output,
fields: map[string]interface{}{},
level: l.level,
output: l.output,
fields: map[string]interface{}{},
disablePooling: l.disablePooling,
}
for k, v := range l.fields {
l2.fields[k] = v
Expand All @@ -167,10 +180,12 @@ func (l *logger) Copy() Logger {

// close returns the logger to the pool for reuse
func (l *logger) close() {
l.level = 0
l.output = nil
l.fields = nil
loggerPool.Put(l)
if !l.disablePooling {
l.level = 0
l.output = nil
l.fields = nil
loggerPool.Put(l)
}
}

func (l *logger) send(level Level, calldepth int, msg string, fields map[string]interface{}) {
Expand Down
42 changes: 42 additions & 0 deletions xlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,52 @@ func TestNew(t *testing.T) {
// Ensure l.fields is a clone
c.Fields["bar"] = "baz"
assert.Equal(t, F{"foo": "bar"}, F(l.fields))
assert.Equal(t, false, l.disablePooling)
l.close()
}
}

func TestNewPoolDisabled(t *testing.T) {
oc := NewOutputChannel(newTestOutput())
defer oc.Close()
originalPool := loggerPool
defer func(p *sync.Pool) {
loggerPool = originalPool
}(originalPool)
loggerPool = &sync.Pool{
New: func() interface{} {
assert.Fail(t, "pool used when disabled")
return nil
},
}
c := Config{
Level: LevelError,
Output: oc,
Fields: F{"foo": "bar"},
DisablePooling: true,
}
L := New(c)
l, ok := L.(*logger)
if assert.True(t, ok) {
assert.Equal(t, LevelError, l.level)
assert.Equal(t, c.Output, l.output)
assert.Equal(t, F{"foo": "bar"}, F(l.fields))
// Ensure l.fields is a clone
c.Fields["bar"] = "baz"
assert.Equal(t, F{"foo": "bar"}, F(l.fields))
assert.Equal(t, true, l.disablePooling)
l.close()
// Assert again to ensure close does not remove internal state
assert.Equal(t, LevelError, l.level)
assert.Equal(t, c.Output, l.output)
assert.Equal(t, F{"foo": "bar"}, F(l.fields))
// Ensure l.fields is a clone
c.Fields["bar"] = "baz"
assert.Equal(t, F{"foo": "bar"}, F(l.fields))
assert.Equal(t, true, l.disablePooling)
}
}

func TestCopy(t *testing.T) {
oc := NewOutputChannel(newTestOutput())
defer oc.Close()
Expand Down

0 comments on commit 7ed5dc1

Please sign in to comment.