...

Source file src/k8s.io/klog/v2/klogr/klogr_test.go

Documentation: k8s.io/klog/v2/klogr

     1  package klogr
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"strings"
     8  	"testing"
     9  
    10  	"k8s.io/klog/v2"
    11  	"k8s.io/klog/v2/internal/test/require"
    12  	"k8s.io/klog/v2/test"
    13  
    14  	"github.com/go-logr/logr"
    15  )
    16  
    17  const (
    18  	formatDefault = "Default"
    19  	formatNew     = "New"
    20  )
    21  
    22  func testOutput(t *testing.T, format string) {
    23  	createLogger := func() logr.Logger {
    24  		switch format {
    25  		case formatNew:
    26  			return New()
    27  		case formatDefault:
    28  			return NewWithOptions()
    29  		default:
    30  			return NewWithOptions(WithFormat(Format(format)))
    31  		}
    32  	}
    33  	tests := map[string]struct {
    34  		klogr              logr.Logger
    35  		text               string
    36  		keysAndValues      []interface{}
    37  		err                error
    38  		expectedOutput     string
    39  		expectedKlogOutput string
    40  	}{
    41  		"should log with values passed to keysAndValues": {
    42  			klogr:         createLogger().V(0),
    43  			text:          "test",
    44  			keysAndValues: []interface{}{"akey", "avalue"},
    45  			expectedOutput: `"msg"="test" "akey"="avalue"
    46  `,
    47  			expectedKlogOutput: `"test" akey="avalue"
    48  `,
    49  		},
    50  		"should log with name and values passed to keysAndValues": {
    51  			klogr:         createLogger().V(0).WithName("me"),
    52  			text:          "test",
    53  			keysAndValues: []interface{}{"akey", "avalue"},
    54  			// Sorted by keys.
    55  			expectedOutput: `"msg"="test" "akey"="avalue" "logger"="me"
    56  `,
    57  			// Not sorted by keys.
    58  			expectedKlogOutput: `"test" logger="me" akey="avalue"
    59  `,
    60  		},
    61  		"should log with multiple names and values passed to keysAndValues": {
    62  			klogr:         createLogger().V(0).WithName("hello").WithName("world"),
    63  			text:          "test",
    64  			keysAndValues: []interface{}{"akey", "avalue"},
    65  			// Sorted by keys.
    66  			expectedOutput: `"msg"="test" "akey"="avalue" "logger"="hello.world"
    67  `,
    68  			// Not sorted by keys.
    69  			expectedKlogOutput: `"test" logger="hello.world" akey="avalue"
    70  `,
    71  		},
    72  		"may print duplicate keys with the same value": {
    73  			klogr:         createLogger().V(0),
    74  			text:          "test",
    75  			keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue"},
    76  			expectedOutput: `"msg"="test" "akey"="avalue"
    77  `,
    78  			expectedKlogOutput: `"test" akey="avalue" akey="avalue"
    79  `,
    80  		},
    81  		"may print duplicate keys when the values are passed to Info": {
    82  			klogr:         createLogger().V(0),
    83  			text:          "test",
    84  			keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue2"},
    85  			expectedOutput: `"msg"="test" "akey"="avalue2"
    86  `,
    87  			expectedKlogOutput: `"test" akey="avalue" akey="avalue2"
    88  `,
    89  		},
    90  		"should only print the duplicate key that is passed to Info if one was passed to the logger": {
    91  			klogr:         createLogger().WithValues("akey", "avalue"),
    92  			text:          "test",
    93  			keysAndValues: []interface{}{"akey", "avalue"},
    94  			expectedOutput: `"msg"="test" "akey"="avalue"
    95  `,
    96  			expectedKlogOutput: `"test" akey="avalue"
    97  `,
    98  		},
    99  		"should sort within logger and parameter key/value pairs in the default format and dump the logger pairs first": {
   100  			klogr:         createLogger().WithValues("akey9", "avalue9", "akey8", "avalue8", "akey1", "avalue1"),
   101  			text:          "test",
   102  			keysAndValues: []interface{}{"akey5", "avalue5", "akey4", "avalue4"},
   103  			expectedOutput: `"msg"="test" "akey1"="avalue1" "akey4"="avalue4" "akey5"="avalue5" "akey8"="avalue8" "akey9"="avalue9"
   104  `,
   105  			expectedKlogOutput: `"test" akey9="avalue9" akey8="avalue8" akey1="avalue1" akey5="avalue5" akey4="avalue4"
   106  `,
   107  		},
   108  		"should only print the key passed to Info when one is already set on the logger": {
   109  			klogr:         createLogger().WithValues("akey", "avalue"),
   110  			text:          "test",
   111  			keysAndValues: []interface{}{"akey", "avalue2"},
   112  			expectedOutput: `"msg"="test" "akey"="avalue2"
   113  `,
   114  			expectedKlogOutput: `"test" akey="avalue2"
   115  `,
   116  		},
   117  		"should correctly handle odd-numbers of KVs": {
   118  			klogr:         createLogger(),
   119  			text:          "test",
   120  			keysAndValues: []interface{}{"akey", "avalue", "akey2"},
   121  			expectedOutput: `"msg"="test" "akey"="avalue" "akey2"="(MISSING)"
   122  `,
   123  			expectedKlogOutput: `"test" akey="avalue" akey2="(MISSING)"
   124  `,
   125  		},
   126  		"should correctly handle odd-numbers of KVs in WithValue": {
   127  			klogr:         createLogger().WithValues("keyWithoutValue"),
   128  			text:          "test",
   129  			keysAndValues: []interface{}{"akey", "avalue", "akey2"},
   130  			// klogr format sorts all key/value pairs.
   131  			expectedOutput: `"msg"="test" "akey"="avalue" "akey2"="(MISSING)" "keyWithoutValue"="(MISSING)"
   132  `,
   133  			expectedKlogOutput: `"test" keyWithoutValue="(MISSING)" akey="avalue" akey2="(MISSING)"
   134  `,
   135  		},
   136  		"should correctly html characters": {
   137  			klogr:         createLogger(),
   138  			text:          "test",
   139  			keysAndValues: []interface{}{"akey", "<&>"},
   140  			expectedOutput: `"msg"="test" "akey"="<&>"
   141  `,
   142  			expectedKlogOutput: `"test" akey="<&>"
   143  `,
   144  		},
   145  		"should correctly handle odd-numbers of KVs in both log values and Info args": {
   146  			klogr:         createLogger().WithValues("basekey1", "basevar1", "basekey2"),
   147  			text:          "test",
   148  			keysAndValues: []interface{}{"akey", "avalue", "akey2"},
   149  			// klogr format sorts all key/value pairs.
   150  			expectedOutput: `"msg"="test" "akey"="avalue" "akey2"="(MISSING)" "basekey1"="basevar1" "basekey2"="(MISSING)"
   151  `,
   152  			expectedKlogOutput: `"test" basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)"
   153  `,
   154  		},
   155  		"should correctly print regular error types": {
   156  			klogr:         createLogger().V(0),
   157  			text:          "test",
   158  			keysAndValues: []interface{}{"err", errors.New("whoops")},
   159  			expectedOutput: `"msg"="test" "err"="whoops"
   160  `,
   161  			expectedKlogOutput: `"test" err="whoops"
   162  `,
   163  		},
   164  		"should use MarshalJSON in the default format if an error type implements it": {
   165  			klogr:         createLogger().V(0),
   166  			text:          "test",
   167  			keysAndValues: []interface{}{"err", &customErrorJSON{"whoops"}},
   168  			expectedOutput: `"msg"="test" "err"="WHOOPS"
   169  `,
   170  			expectedKlogOutput: `"test" err="whoops"
   171  `,
   172  		},
   173  		"should correctly print regular error types when using logr.Error": {
   174  			klogr: createLogger().V(0),
   175  			text:  "test",
   176  			err:   errors.New("whoops"),
   177  			expectedOutput: `"msg"="test" "error"="whoops" 
   178  `,
   179  			expectedKlogOutput: `"test" err="whoops"
   180  `,
   181  		},
   182  	}
   183  	for n, test := range tests {
   184  		t.Run(n, func(t *testing.T) {
   185  
   186  			// hijack the klog output
   187  			tmpWriteBuffer := bytes.NewBuffer(nil)
   188  			klog.SetOutput(tmpWriteBuffer)
   189  
   190  			if test.err != nil {
   191  				test.klogr.Error(test.err, test.text, test.keysAndValues...)
   192  			} else {
   193  				test.klogr.Info(test.text, test.keysAndValues...)
   194  			}
   195  
   196  			// call Flush to ensure the text isn't still buffered
   197  			klog.Flush()
   198  
   199  			actual := tmpWriteBuffer.String()
   200  			expectedOutput := test.expectedOutput
   201  			if format == string(FormatKlog) || format == formatDefault {
   202  				expectedOutput = test.expectedKlogOutput
   203  			}
   204  			if actual != expectedOutput {
   205  				t.Errorf("Expected:\n%s\nActual:\n%s\n", expectedOutput, actual)
   206  			}
   207  		})
   208  	}
   209  }
   210  
   211  func TestOutput(t *testing.T) {
   212  	fs := test.InitKlog(t)
   213  	require.NoError(t, fs.Set("skip_headers", "true"))
   214  
   215  	formats := []string{
   216  		formatNew,
   217  		formatDefault,
   218  		string(FormatSerialize),
   219  		string(FormatKlog),
   220  	}
   221  	for _, format := range formats {
   222  		t.Run(format, func(t *testing.T) {
   223  			testOutput(t, format)
   224  		})
   225  	}
   226  }
   227  
   228  type customErrorJSON struct {
   229  	s string
   230  }
   231  
   232  func (e *customErrorJSON) Error() string {
   233  	return e.s
   234  }
   235  
   236  func (e *customErrorJSON) MarshalJSON() ([]byte, error) {
   237  	return json.Marshal(strings.ToUpper(e.s))
   238  }
   239  

View as plain text