1// +build go1.7
2
3package hlog
4
5import (
6	"bytes"
7	"fmt"
8	"io/ioutil"
9	"net/http"
10	"net/url"
11	"testing"
12
13	"reflect"
14
15	"net/http/httptest"
16
17	"github.com/rs/zerolog"
18	"github.com/rs/zerolog/internal/cbor"
19)
20
21func decodeIfBinary(out *bytes.Buffer) string {
22	p := out.Bytes()
23	if len(p) == 0 || p[0] < 0x7F {
24		return out.String()
25	}
26	return cbor.DecodeObjectToStr(p) + "\n"
27}
28
29func TestNewHandler(t *testing.T) {
30	log := zerolog.New(nil).With().
31		Str("foo", "bar").
32		Logger()
33	lh := NewHandler(log)
34	h := lh(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35		l := FromRequest(r)
36		if !reflect.DeepEqual(*l, log) {
37			t.Fail()
38		}
39	}))
40	h.ServeHTTP(nil, &http.Request{})
41}
42
43func TestURLHandler(t *testing.T) {
44	out := &bytes.Buffer{}
45	r := &http.Request{
46		URL: &url.URL{Path: "/path", RawQuery: "foo=bar"},
47	}
48	h := URLHandler("url")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
49		l := FromRequest(r)
50		l.Log().Msg("")
51	}))
52	h = NewHandler(zerolog.New(out))(h)
53	h.ServeHTTP(nil, r)
54	if want, got := `{"url":"/path?foo=bar"}`+"\n", decodeIfBinary(out); want != got {
55		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
56	}
57}
58
59func TestMethodHandler(t *testing.T) {
60	out := &bytes.Buffer{}
61	r := &http.Request{
62		Method: "POST",
63	}
64	h := MethodHandler("method")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
65		l := FromRequest(r)
66		l.Log().Msg("")
67	}))
68	h = NewHandler(zerolog.New(out))(h)
69	h.ServeHTTP(nil, r)
70	if want, got := `{"method":"POST"}`+"\n", decodeIfBinary(out); want != got {
71		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
72	}
73}
74
75func TestRequestHandler(t *testing.T) {
76	out := &bytes.Buffer{}
77	r := &http.Request{
78		Method: "POST",
79		URL:    &url.URL{Path: "/path", RawQuery: "foo=bar"},
80	}
81	h := RequestHandler("request")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
82		l := FromRequest(r)
83		l.Log().Msg("")
84	}))
85	h = NewHandler(zerolog.New(out))(h)
86	h.ServeHTTP(nil, r)
87	if want, got := `{"request":"POST /path?foo=bar"}`+"\n", decodeIfBinary(out); want != got {
88		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
89	}
90}
91
92func TestRemoteAddrHandler(t *testing.T) {
93	out := &bytes.Buffer{}
94	r := &http.Request{
95		RemoteAddr: "1.2.3.4:1234",
96	}
97	h := RemoteAddrHandler("ip")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
98		l := FromRequest(r)
99		l.Log().Msg("")
100	}))
101	h = NewHandler(zerolog.New(out))(h)
102	h.ServeHTTP(nil, r)
103	if want, got := `{"ip":"1.2.3.4"}`+"\n", decodeIfBinary(out); want != got {
104		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
105	}
106}
107
108func TestRemoteAddrHandlerIPv6(t *testing.T) {
109	out := &bytes.Buffer{}
110	r := &http.Request{
111		RemoteAddr: "[2001:db8:a0b:12f0::1]:1234",
112	}
113	h := RemoteAddrHandler("ip")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
114		l := FromRequest(r)
115		l.Log().Msg("")
116	}))
117	h = NewHandler(zerolog.New(out))(h)
118	h.ServeHTTP(nil, r)
119	if want, got := `{"ip":"2001:db8:a0b:12f0::1"}`+"\n", decodeIfBinary(out); want != got {
120		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
121	}
122}
123
124func TestUserAgentHandler(t *testing.T) {
125	out := &bytes.Buffer{}
126	r := &http.Request{
127		Header: http.Header{
128			"User-Agent": []string{"some user agent string"},
129		},
130	}
131	h := UserAgentHandler("ua")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
132		l := FromRequest(r)
133		l.Log().Msg("")
134	}))
135	h = NewHandler(zerolog.New(out))(h)
136	h.ServeHTTP(nil, r)
137	if want, got := `{"ua":"some user agent string"}`+"\n", decodeIfBinary(out); want != got {
138		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
139	}
140}
141
142func TestRefererHandler(t *testing.T) {
143	out := &bytes.Buffer{}
144	r := &http.Request{
145		Header: http.Header{
146			"Referer": []string{"http://foo.com/bar"},
147		},
148	}
149	h := RefererHandler("referer")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
150		l := FromRequest(r)
151		l.Log().Msg("")
152	}))
153	h = NewHandler(zerolog.New(out))(h)
154	h.ServeHTTP(nil, r)
155	if want, got := `{"referer":"http://foo.com/bar"}`+"\n", decodeIfBinary(out); want != got {
156		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
157	}
158}
159
160func TestRequestIDHandler(t *testing.T) {
161	out := &bytes.Buffer{}
162	r := &http.Request{
163		Header: http.Header{
164			"Referer": []string{"http://foo.com/bar"},
165		},
166	}
167	h := RequestIDHandler("id", "Request-Id")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
168		id, ok := IDFromRequest(r)
169		if !ok {
170			t.Fatal("Missing id in request")
171		}
172		if want, got := id.String(), w.Header().Get("Request-Id"); got != want {
173			t.Errorf("Invalid Request-Id header, got: %s, want: %s", got, want)
174		}
175		l := FromRequest(r)
176		l.Log().Msg("")
177		if want, got := fmt.Sprintf(`{"id":"%s"}`+"\n", id), decodeIfBinary(out); want != got {
178			t.Errorf("Invalid log output, got: %s, want: %s", got, want)
179		}
180	}))
181	h = NewHandler(zerolog.New(out))(h)
182	h.ServeHTTP(httptest.NewRecorder(), r)
183}
184
185func TestCustomHeaderHandler(t *testing.T) {
186	out := &bytes.Buffer{}
187	r := &http.Request{
188		Header: http.Header{
189			"X-Request-Id": []string{"514bbe5bb5251c92bd07a9846f4a1ab6"},
190		},
191	}
192	h := CustomHeaderHandler("reqID", "X-Request-Id")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
193		l := FromRequest(r)
194		l.Log().Msg("")
195	}))
196	h = NewHandler(zerolog.New(out))(h)
197	h.ServeHTTP(nil, r)
198	if want, got := `{"reqID":"514bbe5bb5251c92bd07a9846f4a1ab6"}`+"\n", decodeIfBinary(out); want != got {
199		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
200	}
201}
202
203func TestCombinedHandlers(t *testing.T) {
204	out := &bytes.Buffer{}
205	r := &http.Request{
206		Method: "POST",
207		URL:    &url.URL{Path: "/path", RawQuery: "foo=bar"},
208	}
209	h := MethodHandler("method")(RequestHandler("request")(URLHandler("url")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
210		l := FromRequest(r)
211		l.Log().Msg("")
212	}))))
213	h = NewHandler(zerolog.New(out))(h)
214	h.ServeHTTP(nil, r)
215	if want, got := `{"method":"POST","request":"POST /path?foo=bar","url":"/path?foo=bar"}`+"\n", decodeIfBinary(out); want != got {
216		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
217	}
218}
219
220func BenchmarkHandlers(b *testing.B) {
221	r := &http.Request{
222		Method: "POST",
223		URL:    &url.URL{Path: "/path", RawQuery: "foo=bar"},
224	}
225	h1 := URLHandler("url")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
226		l := FromRequest(r)
227		l.Log().Msg("")
228	}))
229	h2 := MethodHandler("method")(RequestHandler("request")(h1))
230	handlers := map[string]http.Handler{
231		"Single":           NewHandler(zerolog.New(ioutil.Discard))(h1),
232		"Combined":         NewHandler(zerolog.New(ioutil.Discard))(h2),
233		"SingleDisabled":   NewHandler(zerolog.New(ioutil.Discard).Level(zerolog.Disabled))(h1),
234		"CombinedDisabled": NewHandler(zerolog.New(ioutil.Discard).Level(zerolog.Disabled))(h2),
235	}
236	for name := range handlers {
237		h := handlers[name]
238		b.Run(name, func(b *testing.B) {
239			for i := 0; i < b.N; i++ {
240				h.ServeHTTP(nil, r)
241			}
242		})
243	}
244}
245
246func BenchmarkDataRace(b *testing.B) {
247	log := zerolog.New(nil).With().
248		Str("foo", "bar").
249		Logger()
250	lh := NewHandler(log)
251	h := lh(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
252		l := FromRequest(r)
253		l.UpdateContext(func(c zerolog.Context) zerolog.Context {
254			return c.Str("bar", "baz")
255		})
256		l.Log().Msg("")
257	}))
258
259	b.RunParallel(func(pb *testing.PB) {
260		for pb.Next() {
261			h.ServeHTTP(nil, &http.Request{})
262		}
263	})
264}
265