1package logrus
2
3import (
4	"bytes"
5	"context"
6	"fmt"
7	"testing"
8	"time"
9
10	"github.com/stretchr/testify/assert"
11)
12
13func TestEntryWithError(t *testing.T) {
14
15	assert := assert.New(t)
16
17	defer func() {
18		ErrorKey = "error"
19	}()
20
21	err := fmt.Errorf("kaboom at layer %d", 4711)
22
23	assert.Equal(err, WithError(err).Data["error"])
24
25	logger := New()
26	logger.Out = &bytes.Buffer{}
27	entry := NewEntry(logger)
28
29	assert.Equal(err, entry.WithError(err).Data["error"])
30
31	ErrorKey = "err"
32
33	assert.Equal(err, entry.WithError(err).Data["err"])
34
35}
36
37func TestEntryWithContext(t *testing.T) {
38	assert := assert.New(t)
39	ctx := context.WithValue(context.Background(), "foo", "bar")
40
41	assert.Equal(ctx, WithContext(ctx).Context)
42
43	logger := New()
44	logger.Out = &bytes.Buffer{}
45	entry := NewEntry(logger)
46
47	assert.Equal(ctx, entry.WithContext(ctx).Context)
48}
49
50func TestEntryWithContextCopiesData(t *testing.T) {
51	assert := assert.New(t)
52
53	// Initialize a parent Entry object with a key/value set in its Data map
54	logger := New()
55	logger.Out = &bytes.Buffer{}
56	parentEntry := NewEntry(logger).WithField("parentKey", "parentValue")
57
58	// Create two children Entry objects from the parent in different contexts
59	ctx1 := context.WithValue(context.Background(), "foo", "bar")
60	childEntry1 := parentEntry.WithContext(ctx1)
61	assert.Equal(ctx1, childEntry1.Context)
62
63	ctx2 := context.WithValue(context.Background(), "bar", "baz")
64	childEntry2 := parentEntry.WithContext(ctx2)
65	assert.Equal(ctx2, childEntry2.Context)
66	assert.NotEqual(ctx1, ctx2)
67
68	// Ensure that data set in the parent Entry are preserved to both children
69	assert.Equal("parentValue", childEntry1.Data["parentKey"])
70	assert.Equal("parentValue", childEntry2.Data["parentKey"])
71
72	// Modify data stored in the child entry
73	childEntry1.Data["childKey"] = "childValue"
74
75	// Verify that data is successfully stored in the child it was set on
76	val, exists := childEntry1.Data["childKey"]
77	assert.True(exists)
78	assert.Equal("childValue", val)
79
80	// Verify that the data change to child 1 has not affected its sibling
81	val, exists = childEntry2.Data["childKey"]
82	assert.False(exists)
83	assert.Empty(val)
84
85	// Verify that the data change to child 1 has not affected its parent
86	val, exists = parentEntry.Data["childKey"]
87	assert.False(exists)
88	assert.Empty(val)
89}
90
91func TestEntryWithTimeCopiesData(t *testing.T) {
92	assert := assert.New(t)
93
94	// Initialize a parent Entry object with a key/value set in its Data map
95	logger := New()
96	logger.Out = &bytes.Buffer{}
97	parentEntry := NewEntry(logger).WithField("parentKey", "parentValue")
98
99	// Create two children Entry objects from the parent with two different times
100	childEntry1 := parentEntry.WithTime(time.Now().AddDate(0, 0, 1))
101	childEntry2 := parentEntry.WithTime(time.Now().AddDate(0, 0, 2))
102
103	// Ensure that data set in the parent Entry are preserved to both children
104	assert.Equal("parentValue", childEntry1.Data["parentKey"])
105	assert.Equal("parentValue", childEntry2.Data["parentKey"])
106
107	// Modify data stored in the child entry
108	childEntry1.Data["childKey"] = "childValue"
109
110	// Verify that data is successfully stored in the child it was set on
111	val, exists := childEntry1.Data["childKey"]
112	assert.True(exists)
113	assert.Equal("childValue", val)
114
115	// Verify that the data change to child 1 has not affected its sibling
116	val, exists = childEntry2.Data["childKey"]
117	assert.False(exists)
118	assert.Empty(val)
119
120	// Verify that the data change to child 1 has not affected its parent
121	val, exists = parentEntry.Data["childKey"]
122	assert.False(exists)
123	assert.Empty(val)
124}
125
126func TestEntryPanicln(t *testing.T) {
127	errBoom := fmt.Errorf("boom time")
128
129	defer func() {
130		p := recover()
131		assert.NotNil(t, p)
132
133		switch pVal := p.(type) {
134		case *Entry:
135			assert.Equal(t, "kaboom", pVal.Message)
136			assert.Equal(t, errBoom, pVal.Data["err"])
137		default:
138			t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
139		}
140	}()
141
142	logger := New()
143	logger.Out = &bytes.Buffer{}
144	entry := NewEntry(logger)
145	entry.WithField("err", errBoom).Panicln("kaboom")
146}
147
148func TestEntryPanicf(t *testing.T) {
149	errBoom := fmt.Errorf("boom again")
150
151	defer func() {
152		p := recover()
153		assert.NotNil(t, p)
154
155		switch pVal := p.(type) {
156		case *Entry:
157			assert.Equal(t, "kaboom true", pVal.Message)
158			assert.Equal(t, errBoom, pVal.Data["err"])
159		default:
160			t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
161		}
162	}()
163
164	logger := New()
165	logger.Out = &bytes.Buffer{}
166	entry := NewEntry(logger)
167	entry.WithField("err", errBoom).Panicf("kaboom %v", true)
168}
169
170func TestEntryPanic(t *testing.T) {
171	errBoom := fmt.Errorf("boom again")
172
173	defer func() {
174		p := recover()
175		assert.NotNil(t, p)
176
177		switch pVal := p.(type) {
178		case *Entry:
179			assert.Equal(t, "kaboom", pVal.Message)
180			assert.Equal(t, errBoom, pVal.Data["err"])
181		default:
182			t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
183		}
184	}()
185
186	logger := New()
187	logger.Out = &bytes.Buffer{}
188	entry := NewEntry(logger)
189	entry.WithField("err", errBoom).Panic("kaboom")
190}
191
192const (
193	badMessage   = "this is going to panic"
194	panicMessage = "this is broken"
195)
196
197type panickyHook struct{}
198
199func (p *panickyHook) Levels() []Level {
200	return []Level{InfoLevel}
201}
202
203func (p *panickyHook) Fire(entry *Entry) error {
204	if entry.Message == badMessage {
205		panic(panicMessage)
206	}
207
208	return nil
209}
210
211func TestEntryHooksPanic(t *testing.T) {
212	logger := New()
213	logger.Out = &bytes.Buffer{}
214	logger.Level = InfoLevel
215	logger.Hooks.Add(&panickyHook{})
216
217	defer func() {
218		p := recover()
219		assert.NotNil(t, p)
220		assert.Equal(t, panicMessage, p)
221
222		entry := NewEntry(logger)
223		entry.Info("another message")
224	}()
225
226	entry := NewEntry(logger)
227	entry.Info(badMessage)
228}
229
230func TestEntryWithIncorrectField(t *testing.T) {
231	assert := assert.New(t)
232
233	fn := func() {}
234
235	e := Entry{Logger: New()}
236	eWithFunc := e.WithFields(Fields{"func": fn})
237	eWithFuncPtr := e.WithFields(Fields{"funcPtr": &fn})
238
239	assert.Equal(eWithFunc.err, `can not add field "func"`)
240	assert.Equal(eWithFuncPtr.err, `can not add field "funcPtr"`)
241
242	eWithFunc = eWithFunc.WithField("not_a_func", "it is a string")
243	eWithFuncPtr = eWithFuncPtr.WithField("not_a_func", "it is a string")
244
245	assert.Equal(eWithFunc.err, `can not add field "func"`)
246	assert.Equal(eWithFuncPtr.err, `can not add field "funcPtr"`)
247
248	eWithFunc = eWithFunc.WithTime(time.Now())
249	eWithFuncPtr = eWithFuncPtr.WithTime(time.Now())
250
251	assert.Equal(eWithFunc.err, `can not add field "func"`)
252	assert.Equal(eWithFuncPtr.err, `can not add field "funcPtr"`)
253}
254
255func TestEntryLogfLevel(t *testing.T) {
256	logger := New()
257	buffer := &bytes.Buffer{}
258	logger.Out = buffer
259	logger.SetLevel(InfoLevel)
260	entry := NewEntry(logger)
261
262	entry.Logf(DebugLevel, "%s", "debug")
263	assert.NotContains(t, buffer.String(), "debug")
264
265	entry.Logf(WarnLevel, "%s", "warn")
266	assert.Contains(t, buffer.String(), "warn")
267}
268
269func TestEntryReportCallerRace(t *testing.T) {
270	logger := New()
271	entry := NewEntry(logger)
272	go func() {
273		logger.SetReportCaller(true)
274	}()
275	go func() {
276		entry.Info("should not race")
277	}()
278}
279