sigs.k8s.io/controller-runtime@v0.18.2/pkg/log/zap/zap_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package zap 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "flag" 23 "os" 24 "reflect" 25 26 "github.com/go-logr/logr" 27 . "github.com/onsi/ginkgo/v2" 28 . "github.com/onsi/gomega" 29 "go.uber.org/zap/zapcore" 30 corev1 "k8s.io/api/core/v1" 31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 32 "k8s.io/apimachinery/pkg/types" 33 ) 34 35 // testStringer is a fmt.Stringer. 36 type testStringer struct{} 37 38 func (testStringer) String() string { 39 return "value" 40 } 41 42 // fakeSyncWriter is a fake zap.SyncerWriter that lets us test if sync was called. 43 type fakeSyncWriter bool 44 45 func (w *fakeSyncWriter) Write(p []byte) (int, error) { 46 return len(p), nil 47 } 48 49 func (w *fakeSyncWriter) Sync() error { 50 *w = true 51 return nil 52 } 53 54 // logInfo is the information for a particular fakeLogger message. 55 type logInfo struct { 56 name []string 57 tags []interface{} 58 msg string 59 } 60 61 // fakeLoggerRoot is the root object to which all fakeLoggers record their messages. 62 type fakeLoggerRoot struct { 63 messages []logInfo 64 } 65 66 var _ logr.LogSink = &fakeLogger{} 67 68 // fakeLogger is a fake implementation of logr.Logger that records 69 // messages, tags, and names, 70 // just records the name. 71 type fakeLogger struct { 72 name []string 73 tags []interface{} 74 75 root *fakeLoggerRoot 76 } 77 78 func (f *fakeLogger) Init(info logr.RuntimeInfo) { 79 } 80 81 func (f *fakeLogger) WithName(name string) logr.LogSink { 82 names := append([]string(nil), f.name...) 83 names = append(names, name) 84 return &fakeLogger{ 85 name: names, 86 tags: f.tags, 87 root: f.root, 88 } 89 } 90 91 func (f *fakeLogger) WithValues(vals ...interface{}) logr.LogSink { 92 tags := append([]interface{}(nil), f.tags...) 93 tags = append(tags, vals...) 94 return &fakeLogger{ 95 name: f.name, 96 tags: tags, 97 root: f.root, 98 } 99 } 100 101 func (f *fakeLogger) Error(err error, msg string, vals ...interface{}) { 102 tags := append([]interface{}(nil), f.tags...) 103 tags = append(tags, "error", err) 104 tags = append(tags, vals...) 105 f.root.messages = append(f.root.messages, logInfo{ 106 name: append([]string(nil), f.name...), 107 tags: tags, 108 msg: msg, 109 }) 110 } 111 112 func (f *fakeLogger) Info(level int, msg string, vals ...interface{}) { 113 tags := append([]interface{}(nil), f.tags...) 114 tags = append(tags, vals...) 115 f.root.messages = append(f.root.messages, logInfo{ 116 name: append([]string(nil), f.name...), 117 tags: tags, 118 msg: msg, 119 }) 120 } 121 122 func (f *fakeLogger) Enabled(level int) bool { return true } 123 func (f *fakeLogger) V(lvl int) logr.LogSink { return f } 124 125 var _ = Describe("Zap options setup", func() { 126 var opts *Options 127 128 BeforeEach(func() { 129 opts = &Options{} 130 }) 131 132 It("should enable development mode", func() { 133 UseDevMode(true)(opts) 134 Expect(opts.Development).To(BeTrue()) 135 }) 136 137 It("should disable development mode", func() { 138 UseDevMode(false)(opts) 139 Expect(opts.Development).To(BeFalse()) 140 }) 141 142 It("should set a custom writer", func() { 143 var w fakeSyncWriter 144 WriteTo(&w)(opts) 145 Expect(opts.DestWriter).To(Equal(&w)) 146 }) 147 }) 148 149 var _ = Describe("Zap logger setup", func() { 150 Context("when logging kubernetes objects", func() { 151 var logOut *bytes.Buffer 152 var logger logr.Logger 153 154 defineTests := func() { 155 It("should log a standard namespaced Kubernetes object name and namespace", func() { 156 pod := &corev1.Pod{} 157 pod.Name = "some-pod" 158 pod.Namespace = "some-ns" 159 logger.Info("here's a kubernetes object", "thing", pod) 160 161 outRaw := logOut.Bytes() 162 res := map[string]interface{}{} 163 Expect(json.Unmarshal(outRaw, &res)).To(Succeed()) 164 165 Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{ 166 "name": pod.Name, 167 "namespace": pod.Namespace, 168 })) 169 }) 170 171 It("should work fine with normal stringers", func() { 172 logger.Info("here's a non-kubernetes stringer", "thing", testStringer{}) 173 outRaw := logOut.Bytes() 174 res := map[string]interface{}{} 175 Expect(json.Unmarshal(outRaw, &res)).To(Succeed()) 176 177 Expect(res).To(HaveKeyWithValue("thing", "value")) 178 }) 179 180 It("should log a standard non-namespaced Kubernetes object name", func() { 181 node := &corev1.Node{} 182 node.Name = "some-node" 183 logger.Info("here's a kubernetes object", "thing", node) 184 185 outRaw := logOut.Bytes() 186 res := map[string]interface{}{} 187 Expect(json.Unmarshal(outRaw, &res)).To(Succeed()) 188 189 Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{ 190 "name": node.Name, 191 })) 192 }) 193 194 It("should log a standard Kubernetes object's kind, if set", func() { 195 node := &corev1.Node{} 196 node.Name = "some-node" 197 node.APIVersion = "v1" 198 node.Kind = "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 "apiVersion": "v1", 208 "kind": "Node", 209 })) 210 }) 211 212 It("should log a standard non-namespaced NamespacedName name", func() { 213 name := types.NamespacedName{Name: "some-node"} 214 logger.Info("here's a kubernetes object", "thing", name) 215 216 outRaw := logOut.Bytes() 217 res := map[string]interface{}{} 218 Expect(json.Unmarshal(outRaw, &res)).To(Succeed()) 219 220 Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{ 221 "name": name.Name, 222 })) 223 }) 224 225 It("should log an unstructured Kubernetes object", func() { 226 pod := &unstructured.Unstructured{ 227 Object: map[string]interface{}{ 228 "metadata": map[string]interface{}{ 229 "name": "some-pod", 230 "namespace": "some-ns", 231 }, 232 }, 233 } 234 logger.Info("here's a kubernetes object", "thing", pod) 235 236 outRaw := logOut.Bytes() 237 res := map[string]interface{}{} 238 Expect(json.Unmarshal(outRaw, &res)).To(Succeed()) 239 240 Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{ 241 "name": "some-pod", 242 "namespace": "some-ns", 243 })) 244 }) 245 246 It("should log a standard namespaced NamespacedName name and namespace", func() { 247 name := types.NamespacedName{Name: "some-pod", Namespace: "some-ns"} 248 logger.Info("here's a kubernetes object", "thing", name) 249 250 outRaw := logOut.Bytes() 251 res := map[string]interface{}{} 252 Expect(json.Unmarshal(outRaw, &res)).To(Succeed()) 253 254 Expect(res).To(HaveKeyWithValue("thing", map[string]interface{}{ 255 "name": name.Name, 256 "namespace": name.Namespace, 257 })) 258 }) 259 260 It("should not panic with nil obj", func() { 261 var pod *corev1.Pod 262 logger.Info("here's a kubernetes object", "thing", pod) 263 264 outRaw := logOut.Bytes() 265 Expect(string(outRaw)).Should(ContainSubstring("got nil for runtime.Object")) 266 }) 267 } 268 269 Context("with logger created using New", func() { 270 BeforeEach(func() { 271 logOut = new(bytes.Buffer) 272 By("setting up the logger") 273 // use production settings (false) to get just json output 274 logger = New(WriteTo(logOut), UseDevMode(false)) 275 }) 276 defineTests() 277 }) 278 }) 279 }) 280 281 var _ = Describe("Zap log level flag options setup", func() { 282 var ( 283 fromFlags Options 284 fs flag.FlagSet 285 logInfoLevel0 = "info text" 286 logDebugLevel1 = "debug 1 text" 287 logDebugLevel2 = "debug 2 text" 288 logDebugLevel3 = "debug 3 text" 289 ) 290 291 BeforeEach(func() { 292 fromFlags = Options{} 293 fs = *flag.NewFlagSet(os.Args[0], flag.ExitOnError) 294 }) 295 296 Context("with zap-log-level options provided", func() { 297 It("Should output logs for info and debug zap-log-level.", func() { 298 args := []string{"--zap-log-level=debug"} 299 fromFlags.BindFlags(&fs) 300 err := fs.Parse(args) 301 Expect(err).ToNot(HaveOccurred()) 302 logOut := new(bytes.Buffer) 303 304 logger := New(UseFlagOptions(&fromFlags), WriteTo(logOut)) 305 logger.V(0).Info(logInfoLevel0) 306 logger.V(1).Info(logDebugLevel1) 307 308 outRaw := logOut.Bytes() 309 310 Expect(string(outRaw)).Should(ContainSubstring(logInfoLevel0)) 311 Expect(string(outRaw)).Should(ContainSubstring(logDebugLevel1)) 312 }) 313 314 It("Should output only error logs, otherwise empty logs", func() { 315 args := []string{"--zap-log-level=error"} 316 fromFlags.BindFlags(&fs) 317 err := fs.Parse(args) 318 Expect(err).ToNot(HaveOccurred()) 319 320 logOut := new(bytes.Buffer) 321 322 logger := New(UseFlagOptions(&fromFlags), WriteTo(logOut)) 323 logger.V(0).Info(logInfoLevel0) 324 logger.V(1).Info(logDebugLevel1) 325 326 outRaw := logOut.Bytes() 327 328 Expect(outRaw).To(BeEmpty()) 329 }) 330 }) 331 332 Context("with zap-log-level with increased verbosity.", func() { 333 It("Should output debug and info log, with default production mode.", func() { 334 args := []string{"--zap-log-level=1"} 335 fromFlags.BindFlags(&fs) 336 err := fs.Parse(args) 337 Expect(err).ToNot(HaveOccurred()) 338 logOut := new(bytes.Buffer) 339 340 logger := New(UseFlagOptions(&fromFlags), WriteTo(logOut)) 341 logger.V(0).Info(logInfoLevel0) 342 logger.V(1).Info(logDebugLevel1) 343 344 outRaw := logOut.Bytes() 345 346 Expect(string(outRaw)).Should(ContainSubstring(logInfoLevel0)) 347 Expect(string(outRaw)).Should(ContainSubstring(logDebugLevel1)) 348 }) 349 350 It("Should output info and debug logs, with development mode.", func() { 351 args := []string{"--zap-log-level=1", "--zap-devel=true"} 352 fromFlags.BindFlags(&fs) 353 err := fs.Parse(args) 354 Expect(err).ToNot(HaveOccurred()) 355 logOut := new(bytes.Buffer) 356 357 logger := New(UseFlagOptions(&fromFlags), WriteTo(logOut)) 358 logger.V(0).Info(logInfoLevel0) 359 logger.V(1).Info(logDebugLevel1) 360 361 outRaw := logOut.Bytes() 362 363 Expect(string(outRaw)).Should(ContainSubstring(logInfoLevel0)) 364 Expect(string(outRaw)).Should(ContainSubstring(logDebugLevel1)) 365 }) 366 367 It("Should output info, and debug logs with increased verbosity, and with development mode set to true.", func() { 368 args := []string{"--zap-log-level=3", "--zap-devel=false"} 369 fromFlags.BindFlags(&fs) 370 err := fs.Parse(args) 371 Expect(err).ToNot(HaveOccurred()) 372 logOut := new(bytes.Buffer) 373 374 logger := New(UseFlagOptions(&fromFlags), WriteTo(logOut)) 375 logger.V(0).Info(logInfoLevel0) 376 logger.V(1).Info(logDebugLevel1) 377 logger.V(2).Info(logDebugLevel2) 378 logger.V(3).Info(logDebugLevel3) 379 380 outRaw := logOut.Bytes() 381 382 Expect(string(outRaw)).Should(ContainSubstring(logInfoLevel0)) 383 Expect(string(outRaw)).Should(ContainSubstring(logDebugLevel1)) 384 Expect(string(outRaw)).Should(ContainSubstring(logDebugLevel2)) 385 Expect(string(outRaw)).Should(ContainSubstring(logDebugLevel3)) 386 }) 387 It("Should output info, and debug logs with increased verbosity, and with production mode set to true.", func() { 388 args := []string{"--zap-log-level=3", "--zap-devel=true"} 389 fromFlags.BindFlags(&fs) 390 err := fs.Parse(args) 391 Expect(err).ToNot(HaveOccurred()) 392 logOut := new(bytes.Buffer) 393 394 logger := New(UseFlagOptions(&fromFlags), WriteTo(logOut)) 395 logger.V(0).Info(logInfoLevel0) 396 logger.V(1).Info(logDebugLevel1) 397 logger.V(2).Info(logDebugLevel2) 398 logger.V(3).Info(logDebugLevel3) 399 400 outRaw := logOut.Bytes() 401 402 Expect(string(outRaw)).Should(ContainSubstring(logInfoLevel0)) 403 Expect(string(outRaw)).Should(ContainSubstring(logDebugLevel1)) 404 Expect(string(outRaw)).Should(ContainSubstring(logDebugLevel2)) 405 Expect(string(outRaw)).Should(ContainSubstring(logDebugLevel3)) 406 }) 407 }) 408 409 Context("with zap-stacktrace-level options provided", func() { 410 It("Should output stacktrace at info level, with development mode set to true.", func() { 411 args := []string{"--zap-stacktrace-level=info", "--zap-devel=true"} 412 fromFlags.BindFlags(&fs) 413 err := fs.Parse(args) 414 Expect(err).ToNot(HaveOccurred()) 415 out := Options{} 416 UseFlagOptions(&fromFlags)(&out) 417 418 Expect(out.StacktraceLevel.Enabled(zapcore.InfoLevel)).To(BeTrue()) 419 }) 420 421 It("Should output stacktrace at error level, with development mode set to true.", func() { 422 args := []string{"--zap-stacktrace-level=error", "--zap-devel=true"} 423 fromFlags.BindFlags(&fs) 424 err := fs.Parse(args) 425 Expect(err).ToNot(HaveOccurred()) 426 out := Options{} 427 UseFlagOptions(&fromFlags)(&out) 428 429 Expect(out.StacktraceLevel.Enabled(zapcore.ErrorLevel)).To(BeTrue()) 430 }) 431 432 It("Should output stacktrace at panic level, with development mode set to true.", func() { 433 args := []string{"--zap-stacktrace-level=panic", "--zap-devel=true"} 434 fromFlags.BindFlags(&fs) 435 err := fs.Parse(args) 436 Expect(err).ToNot(HaveOccurred()) 437 out := Options{} 438 UseFlagOptions(&fromFlags)(&out) 439 440 Expect(out.StacktraceLevel.Enabled(zapcore.PanicLevel)).To(BeTrue()) 441 Expect(out.StacktraceLevel.Enabled(zapcore.ErrorLevel)).To(BeFalse()) 442 Expect(out.StacktraceLevel.Enabled(zapcore.InfoLevel)).To(BeFalse()) 443 }) 444 }) 445 446 Context("with only -zap-devel flag provided", func() { 447 It("Should set dev=true.", func() { 448 args := []string{"--zap-devel=true"} 449 fromFlags.BindFlags(&fs) 450 if err := fs.Parse(args); err != nil { 451 Expect(err).ToNot(HaveOccurred()) 452 } 453 out := Options{} 454 UseFlagOptions(&fromFlags)(&out) 455 456 Expect(out.Development).To(BeTrue()) 457 Expect(out.Encoder).To(BeNil()) 458 Expect(out.Level).To(BeNil()) 459 Expect(out.StacktraceLevel).To(BeNil()) 460 Expect(out.EncoderConfigOptions).To(BeNil()) 461 }) 462 It("Should set dev=false", func() { 463 args := []string{"--zap-devel=false"} 464 fromFlags.BindFlags(&fs) 465 if err := fs.Parse(args); err != nil { 466 Expect(err).ToNot(HaveOccurred()) 467 } 468 out := Options{} 469 UseFlagOptions(&fromFlags)(&out) 470 471 Expect(out.Development).To(BeFalse()) 472 Expect(out.Encoder).To(BeNil()) 473 Expect(out.Level).To(BeNil()) 474 Expect(out.StacktraceLevel).To(BeNil()) 475 Expect(out.EncoderConfigOptions).To(BeNil()) 476 }) 477 }) 478 479 Context("with zap-time-encoding flag provided", func() { 480 It("Should set time encoder in options", func() { 481 args := []string{"--zap-time-encoding=rfc3339"} 482 fromFlags.BindFlags(&fs) 483 err := fs.Parse(args) 484 Expect(err).ToNot(HaveOccurred()) 485 486 opt := Options{} 487 UseFlagOptions(&fromFlags)(&opt) 488 opt.addDefaults() 489 490 optVal := reflect.ValueOf(opt.TimeEncoder) 491 expVal := reflect.ValueOf(zapcore.RFC3339TimeEncoder) 492 493 Expect(optVal.Pointer()).To(Equal(expVal.Pointer())) 494 }) 495 496 It("Should default to 'rfc3339' time encoding", func() { 497 args := []string{""} 498 fromFlags.BindFlags(&fs) 499 err := fs.Parse(args) 500 Expect(err).ToNot(HaveOccurred()) 501 502 opt := Options{} 503 UseFlagOptions(&fromFlags)(&opt) 504 opt.addDefaults() 505 506 optVal := reflect.ValueOf(opt.TimeEncoder) 507 expVal := reflect.ValueOf(zapcore.RFC3339TimeEncoder) 508 509 Expect(optVal.Pointer()).To(Equal(expVal.Pointer())) 510 }) 511 512 It("Should return an error message, with unknown time-encoding", func() { 513 fs = *flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 514 args := []string{"--zap-time-encoding=foobar"} 515 fromFlags.BindFlags(&fs) 516 err := fs.Parse(args) 517 Expect(err).To(HaveOccurred()) 518 }) 519 520 It("Should propagate time encoder to logger", func() { 521 // zaps ISO8601TimeEncoder uses 2006-01-02T15:04:05.000Z0700 as pattern for iso8601 encoding 522 iso8601Pattern := `^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}([-+][0-9]{4}|Z)` 523 524 args := []string{"--zap-time-encoding=iso8601"} 525 fromFlags.BindFlags(&fs) 526 err := fs.Parse(args) 527 Expect(err).ToNot(HaveOccurred()) 528 logOut := new(bytes.Buffer) 529 530 logger := New(UseFlagOptions(&fromFlags), WriteTo(logOut)) 531 logger.Info("This is a test message") 532 533 outRaw := logOut.Bytes() 534 535 res := map[string]interface{}{} 536 Expect(json.Unmarshal(outRaw, &res)).To(Succeed()) 537 Expect(res["ts"]).Should(MatchRegexp(iso8601Pattern)) 538 }) 539 }) 540 541 Context("with encoder options provided programmatically", func() { 542 It("Should set JSON Encoder, with given Millis TimeEncoder option, and MessageKey", func() { 543 logOut := new(bytes.Buffer) 544 f := func(ec *zapcore.EncoderConfig) { 545 ec.MessageKey = "MillisTimeFormat" 546 } 547 opts := func(o *Options) { 548 o.EncoderConfigOptions = append(o.EncoderConfigOptions, f) 549 } 550 log := New(UseDevMode(false), WriteTo(logOut), opts) 551 log.Info("This is a test message") 552 outRaw := logOut.Bytes() 553 // Assert for JSON Encoder 554 res := map[string]interface{}{} 555 Expect(json.Unmarshal(outRaw, &res)).To(Succeed()) 556 // Assert for MessageKey 557 Expect(string(outRaw)).Should(ContainSubstring("MillisTimeFormat")) 558 }) 559 560 Context("using Level()", func() { 561 var logOut *bytes.Buffer 562 563 BeforeEach(func() { 564 logOut = new(bytes.Buffer) 565 }) 566 567 It("logs with negative logr level", func() { 568 By("setting up the logger") 569 logger := New(WriteTo(logOut), Level(zapcore.Level(-3))) 570 logger.V(3).Info("test 3") // Should be logged 571 Expect(logOut.String()).To(ContainSubstring(`"msg":"test 3"`)) 572 logOut.Truncate(0) 573 logger.V(1).Info("test 1") // Should be logged 574 Expect(logOut.String()).To(ContainSubstring(`"msg":"test 1"`)) 575 logOut.Truncate(0) 576 logger.V(4).Info("test 4") // Should not be logged 577 Expect(logOut.String()).To(BeEmpty()) 578 logger.V(-3).Info("test -3") 579 Expect(logOut.String()).To(ContainSubstring("test -3")) 580 }) 581 It("does not log with positive logr level", func() { 582 By("setting up the logger") 583 logger := New(WriteTo(logOut), Level(zapcore.Level(1))) 584 logger.V(1).Info("test 1") 585 Expect(logOut.String()).To(BeEmpty()) 586 logger.V(3).Info("test 3") 587 Expect(logOut.String()).To(BeEmpty()) 588 }) 589 }) 590 }) 591 })