Skip to content

Commit 88d56b6

Browse files
authored
Merge pull request #1229 from sirupsen/fix-data-entry-race
fix for entry data field race condition
2 parents f513f99 + ac6e35b commit 88d56b6

File tree

5 files changed

+78
-33
lines changed

5 files changed

+78
-33
lines changed

entry.go

+39-27
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,20 @@ func NewEntry(logger *Logger) *Entry {
7878
}
7979
}
8080

81+
func (entry *Entry) Dup() *Entry {
82+
data := make(Fields, len(entry.Data))
83+
for k, v := range entry.Data {
84+
data[k] = v
85+
}
86+
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
87+
}
88+
8189
// Returns the bytes representation of this entry from the formatter.
8290
func (entry *Entry) Bytes() ([]byte, error) {
91+
return entry.bytes_nolock()
92+
}
93+
94+
func (entry *Entry) bytes_nolock() ([]byte, error) {
8395
return entry.Logger.Formatter.Format(entry)
8496
}
8597

@@ -212,68 +224,68 @@ func (entry Entry) HasCaller() (has bool) {
212224

213225
// This function is not declared with a pointer value because otherwise
214226
// race conditions will occur when using multiple goroutines
215-
func (entry Entry) log(level Level, msg string) {
227+
func (entry *Entry) log(level Level, msg string) {
216228
var buffer *bytes.Buffer
217229

218-
// Default to now, but allow users to override if they want.
219-
//
220-
// We don't have to worry about polluting future calls to Entry#log()
221-
// with this assignment because this function is declared with a
222-
// non-pointer receiver.
223-
if entry.Time.IsZero() {
224-
entry.Time = time.Now()
230+
newEntry := entry.Dup()
231+
232+
if newEntry.Time.IsZero() {
233+
newEntry.Time = time.Now()
225234
}
226235

227-
entry.Level = level
228-
entry.Message = msg
229-
entry.Logger.mu.Lock()
230-
if entry.Logger.ReportCaller {
231-
entry.Caller = getCaller()
236+
newEntry.Level = level
237+
newEntry.Message = msg
238+
239+
newEntry.Logger.mu.Lock()
240+
reportCaller := newEntry.Logger.ReportCaller
241+
newEntry.Logger.mu.Unlock()
242+
243+
if reportCaller {
244+
newEntry.Caller = getCaller()
232245
}
233-
entry.Logger.mu.Unlock()
234246

235-
entry.fireHooks()
247+
newEntry.fireHooks()
236248

237249
buffer = getBuffer()
238250
defer func() {
239-
entry.Buffer = nil
251+
newEntry.Buffer = nil
240252
putBuffer(buffer)
241253
}()
242254
buffer.Reset()
243-
entry.Buffer = buffer
255+
newEntry.Buffer = buffer
244256

245-
entry.write()
257+
newEntry.write()
246258

247-
entry.Buffer = nil
259+
newEntry.Buffer = nil
248260

249261
// To avoid Entry#log() returning a value that only would make sense for
250262
// panic() to use in Entry#Panic(), we avoid the allocation by checking
251263
// directly here.
252264
if level <= PanicLevel {
253-
panic(&entry)
265+
panic(newEntry)
254266
}
255267
}
256268

257269
func (entry *Entry) fireHooks() {
258-
entry.Logger.mu.Lock()
259-
defer entry.Logger.mu.Unlock()
260270
err := entry.Logger.Hooks.Fire(entry.Level, entry)
261271
if err != nil {
262272
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
263273
}
264274
}
265275

266276
func (entry *Entry) write() {
267-
entry.Logger.mu.Lock()
268-
defer entry.Logger.mu.Unlock()
269277
serialized, err := entry.Logger.Formatter.Format(entry)
270278
if err != nil {
271279
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
272280
return
273281
}
274-
if _, err = entry.Logger.Out.Write(serialized); err != nil {
275-
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
276-
}
282+
func() {
283+
entry.Logger.mu.Lock()
284+
defer entry.Logger.mu.Unlock()
285+
if _, err := entry.Logger.Out.Write(serialized); err != nil {
286+
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
287+
}
288+
}()
277289
}
278290

279291
func (entry *Entry) Log(level Level, args ...interface{}) {

entry_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func TestEntryWithIncorrectField(t *testing.T) {
232232

233233
fn := func() {}
234234

235-
e := Entry{}
235+
e := Entry{Logger: New()}
236236
eWithFunc := e.WithFields(Fields{"func": fn})
237237
eWithFuncPtr := e.WithFields(Fields{"funcPtr": &fn})
238238

logger.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
// LogFunction For big messages, it can be more efficient to pass a function
1313
// and only call it if the log level is actually enables rather than
1414
// generating the log message and then checking if the level is enabled
15-
type LogFunction func()[]interface{}
15+
type LogFunction func() []interface{}
1616

1717
type Logger struct {
1818
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a

logger_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestFieldValueError(t *testing.T) {
2525
t.Error("unexpected error", err)
2626
}
2727
_, ok := data[FieldKeyLogrusError]
28-
require.True(t, ok)
28+
require.True(t, ok, `cannot found expected "logrus_error" field: %v`, data)
2929
}
3030

3131
func TestNoFieldValueError(t *testing.T) {

logrus_test.go

+36-3
Original file line numberDiff line numberDiff line change
@@ -588,15 +588,48 @@ func TestLoggingRaceWithHooksOnEntry(t *testing.T) {
588588
logger.AddHook(hook)
589589
entry := logger.WithField("context", "clue")
590590

591-
var wg sync.WaitGroup
591+
var (
592+
wg sync.WaitGroup
593+
mtx sync.Mutex
594+
start bool
595+
)
596+
597+
cond := sync.NewCond(&mtx)
598+
592599
wg.Add(100)
593600

594-
for i := 0; i < 100; i++ {
601+
for i := 0; i < 50; i++ {
602+
go func() {
603+
cond.L.Lock()
604+
for !start {
605+
cond.Wait()
606+
}
607+
cond.L.Unlock()
608+
for j := 0; j < 100; j++ {
609+
entry.Info("info")
610+
}
611+
wg.Done()
612+
}()
613+
}
614+
615+
for i := 0; i < 50; i++ {
595616
go func() {
596-
entry.Info("info")
617+
cond.L.Lock()
618+
for !start {
619+
cond.Wait()
620+
}
621+
cond.L.Unlock()
622+
for j := 0; j < 100; j++ {
623+
entry.WithField("another field", "with some data").Info("info")
624+
}
597625
wg.Done()
598626
}()
599627
}
628+
629+
cond.L.Lock()
630+
start = true
631+
cond.L.Unlock()
632+
cond.Broadcast()
600633
wg.Wait()
601634
}
602635

0 commit comments

Comments
 (0)