1/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package zap
18
19import (
20	"bytes"
21	"encoding/json"
22	"io/ioutil"
23
24	"github.com/go-logr/logr"
25	. "github.com/onsi/ginkgo"
26	. "github.com/onsi/gomega"
27	kapi "k8s.io/api/core/v1"
28	"k8s.io/apimachinery/pkg/types"
29)
30
31// testStringer is a fmt.Stringer
32type testStringer struct{}
33
34func (testStringer) String() string {
35	return "value"
36}
37
38// fakeSyncWriter is a fake zap.SyncerWriter that lets us test if sync was called
39type fakeSyncWriter bool
40
41func (w *fakeSyncWriter) Write(p []byte) (int, error) {
42	return len(p), nil
43}
44func (w *fakeSyncWriter) Sync() error {
45	*w = true
46	return nil
47}
48
49// logInfo is the information for a particular fakeLogger message
50type logInfo struct {
51	name []string
52	tags []interface{}
53	msg  string
54}
55
56// fakeLoggerRoot is the root object to which all fakeLoggers record their messages.
57type fakeLoggerRoot struct {
58	messages []logInfo
59}
60
61var _ logr.Logger = &fakeLogger{}
62
63// fakeLogger is a fake implementation of logr.Logger that records
64// messages, tags, and names,
65// just records the name.
66type fakeLogger struct {
67	name []string
68	tags []interface{}
69
70	root *fakeLoggerRoot
71}
72
73func (f *fakeLogger) WithName(name string) logr.Logger {
74	names := append([]string(nil), f.name...)
75	names = append(names, name)
76	return &fakeLogger{
77		name: names,
78		tags: f.tags,
79		root: f.root,
80	}
81}
82
83func (f *fakeLogger) WithValues(vals ...interface{}) logr.Logger {
84	tags := append([]interface{}(nil), f.tags...)
85	tags = append(tags, vals...)
86	return &fakeLogger{
87		name: f.name,
88		tags: tags,
89		root: f.root,
90	}
91}
92
93func (f *fakeLogger) Error(err error, msg string, vals ...interface{}) {
94	tags := append([]interface{}(nil), f.tags...)
95	tags = append(tags, "error", err)
96	tags = append(tags, vals...)
97	f.root.messages = append(f.root.messages, logInfo{
98		name: append([]string(nil), f.name...),
99		tags: tags,
100		msg:  msg,
101	})
102}
103
104func (f *fakeLogger) Info(msg string, vals ...interface{}) {
105	tags := append([]interface{}(nil), f.tags...)
106	tags = append(tags, vals...)
107	f.root.messages = append(f.root.messages, logInfo{
108		name: append([]string(nil), f.name...),
109		tags: tags,
110		msg:  msg,
111	})
112}
113
114func (f *fakeLogger) Enabled() bool             { return true }
115func (f *fakeLogger) V(lvl int) logr.InfoLogger { return f }
116
117var _ = Describe("Zap options setup", func() {
118	var opts *Options
119
120	BeforeEach(func() {
121		opts = &Options{}
122	})
123
124	It("should enable development mode", func() {
125		UseDevMode(true)(opts)
126		Expect(opts.Development).To(BeTrue())
127	})
128
129	It("should disable development mode", func() {
130		UseDevMode(false)(opts)
131		Expect(opts.Development).To(BeFalse())
132	})
133
134	It("should set a custom writer", func() {
135		var w fakeSyncWriter
136		WriteTo(&w)(opts)
137		Expect(opts.DestWritter).To(Equal(&w))
138	})
139})
140
141var _ = Describe("Zap logger setup", func() {
142	Context("with the default output", func() {
143		It("shouldn't fail when setting up production", func() {
144			Expect(Logger(false)).NotTo(BeNil())
145			Expect(New(UseDevMode(false))).NotTo(BeNil())
146		})
147
148		It("shouldn't fail when setting up development", func() {
149			Expect(Logger(true)).NotTo(BeNil())
150			Expect(New(UseDevMode(true))).NotTo(BeNil())
151		})
152	})
153
154	Context("with custom non-sync output", func() {
155		It("shouldn't fail when setting up production", func() {
156			Expect(LoggerTo(ioutil.Discard, false)).NotTo(BeNil())
157			Expect(New(WriteTo(ioutil.Discard), UseDevMode(false))).NotTo(BeNil())
158		})
159
160		It("shouldn't fail when setting up development", func() {
161			Expect(LoggerTo(ioutil.Discard, true)).NotTo(BeNil())
162			Expect(New(WriteTo(ioutil.Discard), UseDevMode(true))).NotTo(BeNil())
163		})
164	})
165
166	Context("when logging kubernetes objects", func() {
167		var logOut *bytes.Buffer
168		var logger logr.Logger
169
170		defineTests := func() {
171			It("should log a standard namespaced Kubernetes object name and namespace", func() {
172				pod := &kapi.Pod{}
173				pod.Name = "some-pod"
174				pod.Namespace = "some-ns"
175				logger.Info("here's a kubernetes object", "thing", pod)
176
177				outRaw := logOut.Bytes()
178				res := map[string]interface{}{}
179				Expect(json.Unmarshal(outRaw, &res)).To(Succeed())
180
181				Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{
182					"name":      pod.Name,
183					"namespace": pod.Namespace,
184				}))
185			})
186
187			It("should work fine with normal stringers", func() {
188				logger.Info("here's a non-kubernetes stringer", "thing", testStringer{})
189				outRaw := logOut.Bytes()
190				res := map[string]interface{}{}
191				Expect(json.Unmarshal(outRaw, &res)).To(Succeed())
192
193				Expect(res).To(HaveKeyWithValue("thing", "value"))
194			})
195
196			It("should log a standard non-namespaced Kubernetes object name", func() {
197				node := &kapi.Node{}
198				node.Name = "some-node"
199				logger.Info("here's a kubernetes object", "thing", node)
200
201				outRaw := logOut.Bytes()
202				res := map[string]interface{}{}
203				Expect(json.Unmarshal(outRaw, &res)).To(Succeed())
204
205				Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{
206					"name": node.Name,
207				}))
208			})
209
210			It("should log a standard Kubernetes object's kind, if set", func() {
211				node := &kapi.Node{}
212				node.Name = "some-node"
213				node.APIVersion = "v1"
214				node.Kind = "Node"
215				logger.Info("here's a kubernetes object", "thing", node)
216
217				outRaw := logOut.Bytes()
218				res := map[string]interface{}{}
219				Expect(json.Unmarshal(outRaw, &res)).To(Succeed())
220
221				Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{
222					"name":       node.Name,
223					"apiVersion": "v1",
224					"kind":       "Node",
225				}))
226			})
227
228			It("should log a standard non-namespaced NamespacedName name", func() {
229				name := types.NamespacedName{Name: "some-node"}
230				logger.Info("here's a kubernetes object", "thing", name)
231
232				outRaw := logOut.Bytes()
233				res := map[string]interface{}{}
234				Expect(json.Unmarshal(outRaw, &res)).To(Succeed())
235
236				Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{
237					"name": name.Name,
238				}))
239			})
240
241			It("should log a standard namespaced NamespacedName name and namespace", func() {
242				name := types.NamespacedName{Name: "some-pod", Namespace: "some-ns"}
243				logger.Info("here's a kubernetes object", "thing", name)
244
245				outRaw := logOut.Bytes()
246				res := map[string]interface{}{}
247				Expect(json.Unmarshal(outRaw, &res)).To(Succeed())
248
249				Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{
250					"name":      name.Name,
251					"namespace": name.Namespace,
252				}))
253			})
254		}
255
256		Context("with logger created using New", func() {
257			BeforeEach(func() {
258				logOut = new(bytes.Buffer)
259				By("setting up the logger")
260				// use production settings (false) to get just json output
261				logger = New(WriteTo(logOut), UseDevMode(false))
262			})
263			defineTests()
264
265		})
266		Context("with logger created using LoggerTo", func() {
267			BeforeEach(func() {
268				logOut = new(bytes.Buffer)
269				By("setting up the logger")
270				// use production settings (false) to get just json output
271				logger = LoggerTo(logOut, false)
272			})
273			defineTests()
274		})
275	})
276})
277