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 }