github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/common/utils/logging/logger_test.go (about) 1 // Copyright 2020 Google Inc. All Rights Reserved. 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 logging 16 17 import ( 18 "bytes" 19 "fmt" 20 "log" 21 "os" 22 "strings" 23 "testing" 24 "time" 25 26 daisy "github.com/GoogleCloudPlatform/compute-daisy" 27 "github.com/stretchr/testify/assert" 28 29 "github.com/GoogleCloudPlatform/compute-image-tools/proto/go/pb" 30 "github.com/GoogleCloudPlatform/compute-image-tools/proto/go/pbtesting" 31 ) 32 33 var ( 34 dateTime = time.Date(2009, 11, 10, 23, 10, 15, 0, time.UTC) 35 ) 36 37 func Test_RedirectGlobalLogsToUser_CapturesStandardLog(t *testing.T) { 38 // Ensure that previous log settings are overwritten. 39 log.SetPrefix("old-prefix") 40 log.SetFlags(log.LstdFlags) 41 log.SetOutput(os.Stderr) 42 43 logger, buffer := setupTestLogger("prefix", dateTime) 44 45 RedirectGlobalLogsToUser(logger) 46 log.Print("hello world") 47 assert.Equal(t, "prefix: 2009-11-10T23:10:15Z hello world\n", buffer.String()) 48 } 49 50 func Test_DefaultToolLogger_User_FormatsLikeDaisy(t *testing.T) { 51 fromDaisy := (&daisy.LogEntry{ 52 LocalTimestamp: dateTime, 53 WorkflowName: "image-import", 54 Message: "msg", 55 }).String() 56 57 logger, written := setupTestLogger("[image-import]", dateTime) 58 logger.User("msg") 59 fromToolLogger := written.String() 60 61 assert.Equal(t, fromDaisy, fromToolLogger) 62 } 63 64 func Test_DefaultToolLogger_User_Prefixes(t *testing.T) { 65 // Using a colon after the prefix follows the pattern of Daisy's 66 // standard logger. See daisy.LogEntry.String 67 type test struct { 68 name string 69 userPrefix string 70 expectWritten string 71 } 72 73 tests := []test{ 74 { 75 name: "add colon after prefix", 76 userPrefix: "import-image", 77 expectWritten: "import-image: 2009-11-10T23:10:15Z message\n", 78 }, 79 { 80 name: "don't add extra colon", 81 userPrefix: "import-image:", 82 expectWritten: "import-image: 2009-11-10T23:10:15Z message\n", 83 }, 84 { 85 name: "no colon when empty prefix", 86 userPrefix: "", 87 expectWritten: "2009-11-10T23:10:15Z message\n", 88 }, 89 } 90 91 for _, tt := range tests { 92 t.Run(tt.name, func(t *testing.T) { 93 logger, written := setupTestLogger(tt.userPrefix, dateTime) 94 logger.User("message") 95 actual := written.String() 96 assert.Equal(t, tt.expectWritten, actual) 97 }) 98 } 99 } 100 101 func Test_DefaultToolLogger_UserAndDebugInterleave(t *testing.T) { 102 type test struct { 103 name string 104 logCalls func(writer Logger) 105 expectedLogs string 106 } 107 108 tests := []test{ 109 { 110 name: "no logs when empty write", 111 logCalls: func(writer Logger) {}, 112 expectedLogs: "", 113 }, 114 { 115 name: "prepend specified user prefix", 116 logCalls: func(writer Logger) { 117 writer.User("hello user") 118 }, 119 expectedLogs: "[image-import]: 2009-11-10T23:10:15Z hello user\n", 120 }, 121 { 122 name: "prepend a debug prefix", 123 logCalls: func(writer Logger) { 124 writer.Debug("hello debug") 125 }, 126 expectedLogs: "[debug]: 2009-11-10T23:10:15Z hello debug\n", 127 }, 128 { 129 name: "maintain order when multiple writes", 130 logCalls: func(writer Logger) { 131 writer.Debug("hello debug1") 132 writer.User("hello user1") 133 writer.User("hello user2") 134 writer.Debug("hello debug2") 135 }, 136 expectedLogs: "[debug]: 2009-11-10T23:10:15Z hello debug1\n" + 137 "[image-import]: 2009-11-10T23:10:15Z hello user1\n" + 138 "[image-import]: 2009-11-10T23:10:15Z hello user2\n" + 139 "[debug]: 2009-11-10T23:10:15Z hello debug2\n", 140 }, 141 { 142 name: "don't add extra newlines", 143 logCalls: func(writer Logger) { 144 writer.Debug("hello debug1\n") 145 writer.User("hello user1\n") 146 }, 147 expectedLogs: "[debug]: 2009-11-10T23:10:15Z hello debug1\n" + 148 "[image-import]: 2009-11-10T23:10:15Z hello user1\n", 149 }, 150 } 151 152 for _, tt := range tests { 153 t.Run(tt.name, func(t *testing.T) { 154 logger, written := setupTestLogger("[image-import]", dateTime) 155 tt.logCalls(logger) 156 assert.Equal(t, tt.expectedLogs, written.String()) 157 if tt.expectedLogs == "" { 158 assert.Nil(t, logger.ReadOutputInfo().SerialOutputs, 159 "Only append SerialLog when there were logs written.") 160 } else { 161 assert.Equal(t, []string{tt.expectedLogs}, logger.ReadOutputInfo().SerialOutputs, 162 "Create a single SerialOutput member containing all debug and user logs.") 163 } 164 }) 165 } 166 } 167 168 func Test_DefaultToolLogger_Metric_MergesNestedStruct(t *testing.T) { 169 logger := NewToolLogger("[user]") 170 logger.Metric(&pb.OutputInfo{IsUefiDetected: true}) 171 logger.Metric(&pb.OutputInfo{InspectionResults: &pb.InspectionResults{ 172 ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS}}) 173 expected := &pb.OutputInfo{ 174 IsUefiDetected: true, 175 InspectionResults: &pb.InspectionResults{ 176 ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS, 177 }, 178 } 179 pbtesting.AssertEqual(t, expected, logger.ReadOutputInfo()) 180 } 181 182 func Test_DefaultToolLogger_Metric_AppendsSlices(t *testing.T) { 183 logger := NewToolLogger("[user]") 184 logger.Metric(&pb.OutputInfo{InflationTimeMs: []int64{30}}) 185 logger.Metric(&pb.OutputInfo{InflationTimeMs: []int64{40}}) 186 expected := &pb.OutputInfo{InflationTimeMs: []int64{30, 40}} 187 188 pbtesting.AssertEqual(t, expected, logger.ReadOutputInfo()) 189 } 190 191 func Test_DefaultToolLogger_Metric_DoesntClobberSingleValuesWithDefaultValues(t *testing.T) { 192 logger := NewToolLogger("[user]") 193 logger.Metric(&pb.OutputInfo{IsUefiDetected: true}) 194 logger.Metric(&pb.OutputInfo{InflationType: "api"}) 195 expected := &pb.OutputInfo{IsUefiDetected: true, InflationType: "api"} 196 pbtesting.AssertEqual(t, expected, logger.ReadOutputInfo()) 197 } 198 199 func Test_DefaultToolLogger_ReadOutputInfo_ClearsState(t *testing.T) { 200 logger, _ := setupTestLogger("[user]", dateTime) 201 202 // 1. Use the logger; on first read, OutputInfo should contain all buffered information. 203 logger.Metric(&pb.OutputInfo{IsUefiDetected: true}) 204 logger.User("hi") 205 logger.Trace("trace") 206 firstRead := logger.ReadOutputInfo() 207 pbtesting.AssertEqual(t, &pb.OutputInfo{ 208 IsUefiDetected: true, 209 SerialOutputs: []string{"[user]: 2009-11-10T23:10:15Z hi\n", "trace"}, 210 }, firstRead) 211 212 // 2. On second read, the buffers should be cleared. 213 secondRead := logger.ReadOutputInfo() 214 pbtesting.AssertEqual(t, &pb.OutputInfo{}, secondRead) 215 216 // 3. Use the logger again and verify that the new information is kept. 217 logger.Metric(&pb.OutputInfo{InflationType: "daisy"}) 218 logger.User("hi 1") 219 logger.Trace("trace 1") 220 221 thirdRead := logger.ReadOutputInfo() 222 pbtesting.AssertEqual(t, &pb.OutputInfo{ 223 InflationType: "daisy", 224 SerialOutputs: []string{"[user]: 2009-11-10T23:10:15Z hi 1\n", "trace 1"}, 225 }, thirdRead) 226 } 227 228 func Test_DefaultToolLogger_NewLogger_WritesToParent(t *testing.T) { 229 parent, _ := setupTestLogger("[parent-prefix]", dateTime) 230 231 parent.User("user-1") 232 parent.Debug("debug-1") 233 parent.Trace("trace-1") 234 parent.Metric(&pb.OutputInfo{SourcesSizeGb: []int64{1}}) 235 236 child := parent.NewLogger("[child-prefix]") 237 child.User("user-2") 238 child.Debug("debug-2") 239 child.Trace("trace-2") 240 child.Metric(&pb.OutputInfo{SourcesSizeGb: []int64{2}}) 241 242 parent.User("user-3") 243 parent.Debug("debug-3") 244 parent.Trace("trace-3") 245 parent.Metric(&pb.OutputInfo{SourcesSizeGb: []int64{3}}) 246 247 expected := pb.OutputInfo{ 248 SourcesSizeGb: []int64{1, 2, 3}, 249 SerialOutputs: []string{ 250 strings.Join([]string{ 251 "[parent-prefix]: 2009-11-10T23:10:15Z user-1", 252 "[debug]: 2009-11-10T23:10:15Z debug-1", 253 "[child-prefix]: 2009-11-10T23:10:15Z user-2", 254 "[debug]: 2009-11-10T23:10:15Z debug-2", 255 "[parent-prefix]: 2009-11-10T23:10:15Z user-3", 256 "[debug]: 2009-11-10T23:10:15Z debug-3", 257 "", 258 }, "\n"), 259 "trace-1", 260 "trace-2", 261 "trace-3", 262 }, 263 } 264 actual := parent.ReadOutputInfo() 265 pbtesting.AssertEqual(t, &expected, actual) 266 } 267 268 func setupTestLogger(userPrefix string, now time.Time) (ToolLogger, fmt.Stringer) { 269 var buffer bytes.Buffer 270 realLogger := NewToolLogger(userPrefix).(*defaultToolLogger) 271 realLogger.timeProvider = func() time.Time { 272 return now 273 } 274 realLogger.output.SetOutput(&buffer) 275 return realLogger, &buffer 276 }