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