istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/log/default_test.go (about)

     1  // Copyright 2017 Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package log
    16  
    17  import (
    18  	"encoding/json"
    19  	"regexp"
    20  	"strconv"
    21  	"testing"
    22  	"time"
    23  )
    24  
    25  func testOptions() *Options {
    26  	return DefaultOptions()
    27  }
    28  
    29  func TestDefault(t *testing.T) {
    30  	cases := []struct {
    31  		f          func()
    32  		pat        string
    33  		json       bool
    34  		caller     bool
    35  		wantExit   bool
    36  		stackLevel Level
    37  	}{
    38  		{
    39  			f:   func() { Debug("Hello") },
    40  			pat: timePattern + "\tdebug\tHello",
    41  		},
    42  		{
    43  			f:   func() { Debugf("Hello") },
    44  			pat: timePattern + "\tdebug\tHello",
    45  		},
    46  		{
    47  			f:   func() { Debugf("%s", "Hello") },
    48  			pat: timePattern + "\tdebug\tHello",
    49  		},
    50  
    51  		{
    52  			f:   func() { Info("Hello") },
    53  			pat: timePattern + "\tinfo\tHello",
    54  		},
    55  		{
    56  			f:   func() { Infof("Hello") },
    57  			pat: timePattern + "\tinfo\tHello",
    58  		},
    59  		{
    60  			f:   func() { Infof("%s", "Hello") },
    61  			pat: timePattern + "\tinfo\tHello",
    62  		},
    63  		{
    64  			f:   func() { Warn("Hello") },
    65  			pat: timePattern + "\twarn\tHello",
    66  		},
    67  		{
    68  			f:   func() { Warnf("Hello") },
    69  			pat: timePattern + "\twarn\tHello",
    70  		},
    71  		{
    72  			f:   func() { Warnf("%s", "Hello") },
    73  			pat: timePattern + "\twarn\tHello",
    74  		},
    75  
    76  		{
    77  			f:   func() { Error("Hello") },
    78  			pat: timePattern + "\terror\tHello",
    79  		},
    80  		{
    81  			f:   func() { Errorf("Hello") },
    82  			pat: timePattern + "\terror\tHello",
    83  		},
    84  		{
    85  			f:   func() { Errorf("%s", "Hello") },
    86  			pat: timePattern + "\terror\tHello",
    87  		},
    88  
    89  		{
    90  			f:        func() { Fatal("Hello") },
    91  			pat:      timePattern + "\tfatal\tHello",
    92  			wantExit: true,
    93  		},
    94  		{
    95  			f:        func() { Fatalf("Hello") },
    96  			pat:      timePattern + "\tfatal\tHello",
    97  			wantExit: true,
    98  		},
    99  		{
   100  			f:        func() { Fatalf("%s", "Hello") },
   101  			pat:      timePattern + "\tfatal\tHello",
   102  			wantExit: true,
   103  		},
   104  
   105  		{
   106  			f:      func() { Debug("Hello") },
   107  			pat:    timePattern + "\tdebug\tlog/default_test.go:.*\tHello",
   108  			caller: true,
   109  		},
   110  
   111  		{
   112  			f: func() { Debug("Hello") },
   113  			pat: "{\"level\":\"debug\",\"time\":\"" + timePattern + "\",\"caller\":\"log/default_test.go:.*\",\"msg\":\"Hello\"," +
   114  				"\"stack\":\".*\"}",
   115  			json:       true,
   116  			caller:     true,
   117  			stackLevel: DebugLevel,
   118  		},
   119  		{
   120  			f: func() { Info("Hello") },
   121  			pat: "{\"level\":\"info\",\"time\":\"" + timePattern + "\",\"caller\":\"log/default_test.go:.*\",\"msg\":\"Hello\"," +
   122  				"\"stack\":\".*\"}",
   123  			json:       true,
   124  			caller:     true,
   125  			stackLevel: DebugLevel,
   126  		},
   127  		{
   128  			f: func() { Warn("Hello") },
   129  			pat: "{\"level\":\"warn\",\"time\":\"" + timePattern + "\",\"caller\":\"log/default_test.go:.*\",\"msg\":\"Hello\"," +
   130  				"\"stack\":\".*\"}",
   131  			json:       true,
   132  			caller:     true,
   133  			stackLevel: DebugLevel,
   134  		},
   135  		{
   136  			f: func() { Error("Hello") },
   137  			pat: "{\"level\":\"error\",\"time\":\"" + timePattern + "\",\"caller\":\"log/default_test.go:.*\",\"msg\":\"Hello\"," +
   138  				"\"stack\":\".*\"}",
   139  			json:       true,
   140  			caller:     true,
   141  			stackLevel: DebugLevel,
   142  		},
   143  		{
   144  			f: func() { Fatal("Hello") },
   145  			pat: "{\"level\":\"fatal\",\"time\":\"" + timePattern + "\",\"caller\":\"log/default_test.go:.*\",\"msg\":\"Hello\"," +
   146  				"\"stack\":\".*\"}",
   147  			json:       true,
   148  			caller:     true,
   149  			wantExit:   true,
   150  			stackLevel: DebugLevel,
   151  		},
   152  	}
   153  
   154  	for i, c := range cases {
   155  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   156  			var exitCalled bool
   157  			lines, err := captureStdout(func() {
   158  				o := testOptions()
   159  				o.JSONEncoding = c.json
   160  
   161  				if err := Configure(o); err != nil {
   162  					t.Errorf("Got err '%v', expecting success", err)
   163  				}
   164  
   165  				pt := funcs.Load().(patchTable)
   166  				pt.exitProcess = func(_ int) {
   167  					exitCalled = true
   168  				}
   169  				funcs.Store(pt)
   170  
   171  				defaultScope.SetOutputLevel(DebugLevel)
   172  				defaultScope.SetStackTraceLevel(c.stackLevel)
   173  				defaultScope.SetLogCallers(c.caller)
   174  
   175  				c.f()
   176  				_ = Sync()
   177  			})
   178  
   179  			if exitCalled != c.wantExit {
   180  				var verb string
   181  				if c.wantExit {
   182  					verb = " never"
   183  				}
   184  				t.Errorf("os.Exit%s called", verb)
   185  			}
   186  
   187  			if err != nil {
   188  				t.Errorf("Got error '%v', expected success", err)
   189  			}
   190  
   191  			if match, _ := regexp.MatchString(c.pat, lines[0]); !match {
   192  				t.Errorf("Got '%v', expected a match with '%v'", lines[0], c.pat)
   193  			}
   194  		})
   195  	}
   196  }
   197  
   198  func TestEnabled(t *testing.T) {
   199  	cases := []struct {
   200  		level        Level
   201  		debugEnabled bool
   202  		infoEnabled  bool
   203  		warnEnabled  bool
   204  		errorEnabled bool
   205  		fatalEnabled bool
   206  	}{
   207  		{DebugLevel, true, true, true, true, true},
   208  		{InfoLevel, false, true, true, true, true},
   209  		{WarnLevel, false, false, true, true, true},
   210  		{ErrorLevel, false, false, false, true, true},
   211  		{FatalLevel, false, false, false, false, true},
   212  		{NoneLevel, false, false, false, false, false},
   213  	}
   214  
   215  	for i, c := range cases {
   216  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   217  			o := testOptions()
   218  			o.SetDefaultOutputLevel(DefaultScopeName, c.level)
   219  
   220  			_ = Configure(o)
   221  
   222  			pt := funcs.Load().(patchTable)
   223  			pt.exitProcess = func(_ int) {
   224  			}
   225  			funcs.Store(pt)
   226  
   227  			if c.debugEnabled != DebugEnabled() {
   228  				t.Errorf("Got %v, expecting %v", DebugEnabled(), c.debugEnabled)
   229  			}
   230  
   231  			if c.infoEnabled != InfoEnabled() {
   232  				t.Errorf("Got %v, expecting %v", InfoEnabled(), c.infoEnabled)
   233  			}
   234  
   235  			if c.warnEnabled != WarnEnabled() {
   236  				t.Errorf("Got %v, expecting %v", WarnEnabled(), c.warnEnabled)
   237  			}
   238  
   239  			if c.errorEnabled != ErrorEnabled() {
   240  				t.Errorf("Got %v, expecting %v", ErrorEnabled(), c.errorEnabled)
   241  			}
   242  
   243  			if c.fatalEnabled != FatalEnabled() {
   244  				t.Errorf("Got %v, expecting %v", FatalEnabled(), c.fatalEnabled)
   245  			}
   246  		})
   247  	}
   248  }
   249  
   250  func TestDefaultWithLabel(t *testing.T) {
   251  	lines, err := captureStdout(func() {
   252  		Configure(DefaultOptions())
   253  		funcs.Store(funcs.Load().(patchTable))
   254  		WithLabels("foo", "bar").WithLabels("baz", 123, "qux", 0.123).Error("Hello")
   255  
   256  		_ = Sync()
   257  	})
   258  	if err != nil {
   259  		t.Errorf("Got error '%v', expected success", err)
   260  	}
   261  
   262  	mustRegexMatchString(t, lines[0], `Hello	foo=bar baz=123 qux=0.123`)
   263  }
   264  
   265  func TestLogWithTime(t *testing.T) {
   266  	getLogTime := func(t *testing.T, line string) time.Time {
   267  		type logEntry struct {
   268  			Time time.Time `json:"time"`
   269  		}
   270  		var e logEntry
   271  		if err := json.Unmarshal([]byte(line), &e); err != nil {
   272  			t.Fatalf("Failed to unmarshal log entry: %v", err)
   273  		}
   274  		return e.Time
   275  	}
   276  
   277  	t.Run("specified time", func(t *testing.T) {
   278  		yesterday := time.Now().Add(-time.Hour * 24).Truncate(time.Microsecond)
   279  
   280  		stdoutLines, _ := captureStdout(func() {
   281  			o := DefaultOptions()
   282  			o.JSONEncoding = true
   283  			_ = Configure(o)
   284  			defaultScope.LogWithTime(InfoLevel, "Hello", yesterday)
   285  		})
   286  
   287  		gotTime := getLogTime(t, stdoutLines[0])
   288  		if gotTime.UnixNano() != yesterday.UnixNano() {
   289  			t.Fatalf("Got time %v, expected %v", gotTime, yesterday)
   290  		}
   291  	})
   292  
   293  	t.Run("empty time", func(t *testing.T) {
   294  		stdoutLines, _ := captureStdout(func() {
   295  			o := DefaultOptions()
   296  			o.JSONEncoding = true
   297  			_ = Configure(o)
   298  
   299  			var ti time.Time
   300  			defaultScope.LogWithTime(InfoLevel, "Hello", ti)
   301  		})
   302  
   303  		gotTime := getLogTime(t, stdoutLines[0])
   304  		if gotTime.IsZero() {
   305  			t.Fatalf("Got %v, expected non-zero", gotTime)
   306  		}
   307  	})
   308  }