github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/actor/sharedaction/logging_test.go (about) 1 package sharedaction_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/url" 8 "time" 9 10 "code.cloudfoundry.org/cli/actor/sharedaction" 11 "code.cloudfoundry.org/cli/actor/sharedaction/sharedactionfakes" 12 logcache "code.cloudfoundry.org/go-log-cache/v2" 13 "code.cloudfoundry.org/go-loggregator/v9/rpc/loggregator_v2" 14 . "github.com/onsi/ginkgo" 15 . "github.com/onsi/gomega" 16 ) 17 18 var _ = Describe("Logging Actions", func() { 19 var ( 20 fakeLogCacheClient *sharedactionfakes.FakeLogCacheClient 21 ) 22 23 BeforeEach(func() { 24 fakeLogCacheClient = new(sharedactionfakes.FakeLogCacheClient) 25 }) 26 27 Describe("LogMessage", func() { 28 Describe("Staging", func() { 29 When("the log is a staging log", func() { 30 It("returns true", func() { 31 message := *sharedaction.NewLogMessage( 32 "some-message", 33 "OUT", 34 time.Unix(0, 0), 35 "STG", 36 "some-source-instance", 37 ) 38 39 Expect(message.Staging()).To(BeTrue()) 40 }) 41 }) 42 43 When("the log is any other kind of log", func() { 44 It("returns false", func() { 45 message := *sharedaction.NewLogMessage( 46 "some-message", 47 "OUT", 48 time.Unix(0, 0), 49 "APP", 50 "some-source-instance", 51 ) 52 Expect(message.Staging()).To(BeFalse()) 53 }) 54 }) 55 }) 56 }) 57 58 Describe("GetStreamingLogs", func() { 59 var ( 60 expectedAppGUID string 61 62 messages <-chan sharedaction.LogMessage 63 errs <-chan error 64 stopStreaming context.CancelFunc 65 mostRecentTime time.Time 66 mostRecentEnvelope loggregator_v2.Envelope 67 slightlyOlderEnvelope loggregator_v2.Envelope 68 ) 69 70 BeforeEach(func() { 71 expectedAppGUID = "some-app-guid" 72 // 2 seconds in the past to get past Walk delay 73 // Walk delay context: https://github.com/cloudfoundry/cli/blob/283d5fcdefa1806b24f4242adea1fb85871b4c6b/vendor/code.cloudfoundry.org/go-log-cache/walk.go#L74 74 mostRecentTime = time.Now().Add(-2 * time.Second) 75 mostRecentTimestamp := mostRecentTime.UnixNano() 76 slightlyOlderTimestamp := mostRecentTime.Add(-500 * time.Millisecond).UnixNano() 77 78 mostRecentEnvelope = loggregator_v2.Envelope{ 79 Timestamp: mostRecentTimestamp, 80 SourceId: "some-app-guid", 81 InstanceId: "some-source-instance", 82 Message: &loggregator_v2.Envelope_Log{ 83 Log: &loggregator_v2.Log{ 84 Payload: []byte("message-2"), 85 Type: loggregator_v2.Log_OUT, 86 }, 87 }, 88 Tags: map[string]string{ 89 "source_type": "some-source-type", 90 }, 91 } 92 93 slightlyOlderEnvelope = loggregator_v2.Envelope{ 94 Timestamp: slightlyOlderTimestamp, 95 SourceId: "some-app-guid", 96 InstanceId: "some-source-instance", 97 Message: &loggregator_v2.Envelope_Log{ 98 Log: &loggregator_v2.Log{ 99 Payload: []byte("message-1"), 100 Type: loggregator_v2.Log_OUT, 101 }, 102 }, 103 Tags: map[string]string{ 104 "source_type": "some-source-type", 105 }, 106 } 107 }) 108 109 AfterEach(func() { 110 Eventually(messages).Should(BeClosed()) 111 Eventually(errs).Should(BeClosed()) 112 }) 113 114 JustBeforeEach(func() { 115 messages, errs, stopStreaming = sharedaction.GetStreamingLogs(expectedAppGUID, fakeLogCacheClient) 116 }) 117 118 When("receiving logs", func() { 119 var walkStartTime time.Time 120 121 BeforeEach(func() { 122 fakeLogCacheClient.ReadStub = func( 123 ctx context.Context, 124 sourceID string, 125 start time.Time, 126 opts ...logcache.ReadOption, 127 ) ([]*loggregator_v2.Envelope, error) { 128 if fakeLogCacheClient.ReadCallCount() > 2 { 129 stopStreaming() 130 return []*loggregator_v2.Envelope{}, ctx.Err() 131 } 132 133 if start.IsZero() { 134 return []*loggregator_v2.Envelope{&mostRecentEnvelope}, ctx.Err() 135 } 136 137 walkStartTime = start 138 return []*loggregator_v2.Envelope{&slightlyOlderEnvelope, &mostRecentEnvelope}, ctx.Err() 139 } 140 }) 141 142 It("it starts walking at 1 second previous to the mostRecentEnvelope's time", func() { 143 Eventually(messages).Should(BeClosed()) 144 Expect(walkStartTime).To(BeTemporally("~", mostRecentTime.Add(-1*time.Second), time.Millisecond)) 145 }) 146 147 It("converts them to log messages and passes them through the messages channel", func() { 148 Eventually(messages).Should(HaveLen(2)) 149 var message sharedaction.LogMessage 150 Expect(messages).To(Receive(&message)) 151 Expect(message.Message()).To(Equal("message-1")) 152 Expect(messages).To(Receive(&message)) 153 Expect(message.Message()).To(Equal("message-2")) 154 155 Expect(errs).ToNot(Receive()) 156 }) 157 }) 158 159 When("cancelling log streaming", func() { 160 BeforeEach(func() { 161 fakeLogCacheClient.ReadStub = func( 162 ctx context.Context, 163 sourceID string, 164 start time.Time, 165 opts ...logcache.ReadOption, 166 ) ([]*loggregator_v2.Envelope, error) { 167 return []*loggregator_v2.Envelope{}, ctx.Err() 168 } 169 }) 170 171 It("can be called multiple times", func() { 172 Expect(stopStreaming).ToNot(Panic()) 173 Expect(stopStreaming).ToNot(Panic()) 174 }) 175 }) 176 177 Describe("error handling", func() { 178 When("there is an error 'peeking' at log-cache to determine the latest log", func() { 179 BeforeEach(func() { 180 fakeLogCacheClient.ReadStub = func( 181 ctx context.Context, 182 sourceID string, 183 start time.Time, 184 opts ...logcache.ReadOption, 185 ) ([]*loggregator_v2.Envelope, error) { 186 return nil, fmt.Errorf("error number %d", fakeLogCacheClient.ReadCallCount()) 187 } 188 }) 189 190 AfterEach(func() { 191 stopStreaming() 192 }) 193 194 It("passes 5 errors through the errors channel", func() { 195 Eventually(errs, 2*time.Second).Should(HaveLen(5)) 196 Eventually(errs).Should(Receive(MatchError("error number 1"))) 197 Eventually(errs).Should(Receive(MatchError("error number 2"))) 198 Eventually(errs).Should(Receive(MatchError("error number 3"))) 199 Eventually(errs).Should(Receive(MatchError("error number 4"))) 200 Eventually(errs).Should(Receive(MatchError("error number 5"))) 201 Consistently(errs).ShouldNot(Receive()) 202 }) 203 204 It("tries exactly 5 times", func() { 205 Eventually(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(5)) 206 Consistently(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(5)) 207 }) 208 }) 209 210 When("there is an error walking log-cache to retrieve logs", func() { 211 BeforeEach(func() { 212 fakeLogCacheClient.ReadStub = func( 213 ctx context.Context, 214 sourceID string, 215 start time.Time, 216 opts ...logcache.ReadOption, 217 ) ([]*loggregator_v2.Envelope, error) { 218 if start.IsZero() { 219 return []*loggregator_v2.Envelope{&mostRecentEnvelope}, ctx.Err() 220 } 221 return nil, fmt.Errorf("error number %d", fakeLogCacheClient.ReadCallCount()-1) 222 } 223 }) 224 225 AfterEach(func() { 226 stopStreaming() 227 }) 228 229 It("passes 5 errors through the errors channel", func() { 230 Eventually(errs, 2*time.Second).Should(HaveLen(5)) 231 Eventually(errs).Should(Receive(MatchError("error number 1"))) 232 Eventually(errs).Should(Receive(MatchError("error number 2"))) 233 Eventually(errs).Should(Receive(MatchError("error number 3"))) 234 Eventually(errs).Should(Receive(MatchError("error number 4"))) 235 Eventually(errs).Should(Receive(MatchError("error number 5"))) 236 Consistently(errs).ShouldNot(Receive()) 237 }) 238 239 It("tries exactly 5 times", func() { 240 initialPeekingRead := 1 241 walkRetries := 5 242 expectedReadCallCount := initialPeekingRead + walkRetries 243 244 Eventually(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(expectedReadCallCount)) 245 Consistently(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(expectedReadCallCount)) 246 }) 247 }) 248 }) 249 }) 250 251 Describe("GetRecentLogs", func() { 252 When("the application can be found", func() { 253 When("Log Cache returns logs", func() { 254 BeforeEach(func() { 255 messages := []*loggregator_v2.Envelope{ 256 { 257 Timestamp: int64(20), 258 SourceId: "some-app-guid", 259 InstanceId: "some-source-instance", 260 Message: &loggregator_v2.Envelope_Log{ 261 Log: &loggregator_v2.Log{ 262 Payload: []byte("message-2"), 263 Type: loggregator_v2.Log_OUT, 264 }, 265 }, 266 Tags: map[string]string{ 267 "source_type": "some-source-type", 268 }, 269 }, 270 { 271 Timestamp: int64(10), 272 SourceId: "some-app-guid", 273 InstanceId: "some-source-instance", 274 Message: &loggregator_v2.Envelope_Log{ 275 Log: &loggregator_v2.Log{ 276 Payload: []byte("message-1"), 277 Type: loggregator_v2.Log_OUT, 278 }, 279 }, 280 Tags: map[string]string{ 281 "source_type": "some-source-type", 282 }, 283 }, 284 } 285 286 fakeLogCacheClient.ReadReturns(messages, nil) 287 }) 288 289 It("returns all the recent logs and warnings", func() { 290 messages, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient) 291 Expect(err).ToNot(HaveOccurred()) 292 293 Expect(messages[0].Message()).To(Equal("message-1")) 294 Expect(messages[0].Type()).To(Equal("OUT")) 295 Expect(messages[0].Timestamp()).To(Equal(time.Unix(0, 10))) 296 Expect(messages[0].SourceType()).To(Equal("some-source-type")) 297 Expect(messages[0].SourceInstance()).To(Equal("some-source-instance")) 298 299 Expect(messages[1].Message()).To(Equal("message-2")) 300 Expect(messages[1].Type()).To(Equal("OUT")) 301 Expect(messages[1].Timestamp()).To(Equal(time.Unix(0, 20))) 302 Expect(messages[1].SourceType()).To(Equal("some-source-type")) 303 Expect(messages[1].SourceInstance()).To(Equal("some-source-instance")) 304 }) 305 }) 306 307 When("Log Cache returns non-log envelopes", func() { 308 BeforeEach(func() { 309 messages := []*loggregator_v2.Envelope{ 310 { 311 Timestamp: int64(10), 312 SourceId: "some-app-guid", 313 InstanceId: "some-source-instance", 314 Message: &loggregator_v2.Envelope_Counter{}, 315 Tags: map[string]string{ 316 "source_type": "some-source-type", 317 }, 318 }, 319 } 320 321 fakeLogCacheClient.ReadReturns(messages, nil) 322 }) 323 324 It("ignores them", func() { 325 messages, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient) 326 Expect(err).ToNot(HaveOccurred()) 327 Expect(messages).To(BeEmpty()) 328 }) 329 }) 330 331 When("Log Cache errors", func() { 332 BeforeEach(func() { 333 fakeLogCacheClient.ReadReturns(nil, errors.New("some-recent-logs-error")) 334 }) 335 336 It("returns error and warnings", func() { 337 _, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient) 338 Expect(err).To(MatchError("Failed to retrieve logs from Log Cache: some-recent-logs-error")) 339 }) 340 }) 341 342 When("Log Cache returns a resource-exhausted error from grpc", func() { 343 resourceExhaustedErr := errors.New("unexpected status code 429") 344 u := new(url.URL) 345 v := make(url.Values) 346 347 BeforeEach(func() { 348 fakeLogCacheClient.ReadReturns([]*loggregator_v2.Envelope{}, resourceExhaustedErr) 349 }) 350 351 It("attempts to halve numbber of requested logs, and eventually returns error and warnings", func() { 352 _, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient) 353 354 Expect(err).To(MatchError("Failed to retrieve logs from Log Cache: unexpected status code 429")) 355 Expect(fakeLogCacheClient.ReadCallCount()).To(Equal(10)) 356 357 _, _, _, readOptions := fakeLogCacheClient.ReadArgsForCall(0) 358 readOptions[1](u, v) 359 Expect(v.Get("limit")).To(Equal("1000")) 360 361 _, _, _, readOptions = fakeLogCacheClient.ReadArgsForCall(1) 362 readOptions[1](u, v) 363 Expect(v.Get("limit")).To(Equal("500")) 364 }) 365 }) 366 }) 367 }) 368 369 })