1package logrus_test
2
3import (
4	"bytes"
5	"encoding/json"
6	"sync"
7	"testing"
8
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11
12	. "github.com/sirupsen/logrus"
13	. "github.com/sirupsen/logrus/internal/testutils"
14)
15
16type TestHook struct {
17	Fired bool
18}
19
20func (hook *TestHook) Fire(entry *Entry) error {
21	hook.Fired = true
22	return nil
23}
24
25func (hook *TestHook) Levels() []Level {
26	return []Level{
27		TraceLevel,
28		DebugLevel,
29		InfoLevel,
30		WarnLevel,
31		ErrorLevel,
32		FatalLevel,
33		PanicLevel,
34	}
35}
36
37func TestHookFires(t *testing.T) {
38	hook := new(TestHook)
39
40	LogAndAssertJSON(t, func(log *Logger) {
41		log.Hooks.Add(hook)
42		assert.Equal(t, hook.Fired, false)
43
44		log.Print("test")
45	}, func(fields Fields) {
46		assert.Equal(t, hook.Fired, true)
47	})
48}
49
50type ModifyHook struct {
51}
52
53func (hook *ModifyHook) Fire(entry *Entry) error {
54	entry.Data["wow"] = "whale"
55	return nil
56}
57
58func (hook *ModifyHook) Levels() []Level {
59	return []Level{
60		TraceLevel,
61		DebugLevel,
62		InfoLevel,
63		WarnLevel,
64		ErrorLevel,
65		FatalLevel,
66		PanicLevel,
67	}
68}
69
70func TestHookCanModifyEntry(t *testing.T) {
71	hook := new(ModifyHook)
72
73	LogAndAssertJSON(t, func(log *Logger) {
74		log.Hooks.Add(hook)
75		log.WithField("wow", "elephant").Print("test")
76	}, func(fields Fields) {
77		assert.Equal(t, fields["wow"], "whale")
78	})
79}
80
81func TestCanFireMultipleHooks(t *testing.T) {
82	hook1 := new(ModifyHook)
83	hook2 := new(TestHook)
84
85	LogAndAssertJSON(t, func(log *Logger) {
86		log.Hooks.Add(hook1)
87		log.Hooks.Add(hook2)
88
89		log.WithField("wow", "elephant").Print("test")
90	}, func(fields Fields) {
91		assert.Equal(t, fields["wow"], "whale")
92		assert.Equal(t, hook2.Fired, true)
93	})
94}
95
96type SingleLevelModifyHook struct {
97	ModifyHook
98}
99
100func (h *SingleLevelModifyHook) Levels() []Level {
101	return []Level{InfoLevel}
102}
103
104func TestHookEntryIsPristine(t *testing.T) {
105	l := New()
106	b := &bytes.Buffer{}
107	l.Formatter = &JSONFormatter{}
108	l.Out = b
109	l.AddHook(&SingleLevelModifyHook{})
110
111	l.Error("error message")
112	data := map[string]string{}
113	err := json.Unmarshal(b.Bytes(), &data)
114	require.NoError(t, err)
115	_, ok := data["wow"]
116	require.False(t, ok)
117	b.Reset()
118
119	l.Info("error message")
120	data = map[string]string{}
121	err = json.Unmarshal(b.Bytes(), &data)
122	require.NoError(t, err)
123	_, ok = data["wow"]
124	require.True(t, ok)
125	b.Reset()
126
127	l.Error("error message")
128	data = map[string]string{}
129	err = json.Unmarshal(b.Bytes(), &data)
130	require.NoError(t, err)
131	_, ok = data["wow"]
132	require.False(t, ok)
133	b.Reset()
134}
135
136type ErrorHook struct {
137	Fired bool
138}
139
140func (hook *ErrorHook) Fire(entry *Entry) error {
141	hook.Fired = true
142	return nil
143}
144
145func (hook *ErrorHook) Levels() []Level {
146	return []Level{
147		ErrorLevel,
148	}
149}
150
151func TestErrorHookShouldntFireOnInfo(t *testing.T) {
152	hook := new(ErrorHook)
153
154	LogAndAssertJSON(t, func(log *Logger) {
155		log.Hooks.Add(hook)
156		log.Info("test")
157	}, func(fields Fields) {
158		assert.Equal(t, hook.Fired, false)
159	})
160}
161
162func TestErrorHookShouldFireOnError(t *testing.T) {
163	hook := new(ErrorHook)
164
165	LogAndAssertJSON(t, func(log *Logger) {
166		log.Hooks.Add(hook)
167		log.Error("test")
168	}, func(fields Fields) {
169		assert.Equal(t, hook.Fired, true)
170	})
171}
172
173func TestAddHookRace(t *testing.T) {
174	var wg sync.WaitGroup
175	wg.Add(2)
176	hook := new(ErrorHook)
177	LogAndAssertJSON(t, func(log *Logger) {
178		go func() {
179			defer wg.Done()
180			log.AddHook(hook)
181		}()
182		go func() {
183			defer wg.Done()
184			log.Error("test")
185		}()
186		wg.Wait()
187	}, func(fields Fields) {
188		// the line may have been logged
189		// before the hook was added, so we can't
190		// actually assert on the hook
191	})
192}
193
194type HookCallFunc struct {
195	F func()
196}
197
198func (h *HookCallFunc) Levels() []Level {
199	return AllLevels
200}
201
202func (h *HookCallFunc) Fire(e *Entry) error {
203	h.F()
204	return nil
205}
206
207func TestHookFireOrder(t *testing.T) {
208	checkers := []string{}
209	h := LevelHooks{}
210	h.Add(&HookCallFunc{F: func() { checkers = append(checkers, "first hook") }})
211	h.Add(&HookCallFunc{F: func() { checkers = append(checkers, "second hook") }})
212	h.Add(&HookCallFunc{F: func() { checkers = append(checkers, "third hook") }})
213
214	if err := h.Fire(InfoLevel, &Entry{}); err != nil {
215		t.Error("unexpected error:", err)
216	}
217	require.Equal(t, []string{"first hook", "second hook", "third hook"}, checkers)
218}
219