github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/actor/sharedaction/logging.go (about) 1 package sharedaction 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "log" 8 "strings" 9 "time" 10 11 logcache "code.cloudfoundry.org/go-log-cache/v2" 12 "code.cloudfoundry.org/go-log-cache/v2/rpc/logcache_v1" 13 "code.cloudfoundry.org/go-loggregator/v9/rpc/loggregator_v2" 14 "github.com/sirupsen/logrus" 15 ) 16 17 const ( 18 StagingLog = "STG" 19 RecentLogsLines = 1000 20 21 retryCount = 5 22 retryInterval = time.Millisecond * 250 23 ) 24 25 type LogMessage struct { 26 message string 27 messageType string 28 timestamp time.Time 29 sourceType string 30 sourceInstance string 31 } 32 33 func (log LogMessage) Message() string { 34 return log.message 35 } 36 37 func (log LogMessage) Type() string { 38 return log.messageType 39 } 40 41 func (log LogMessage) Staging() bool { 42 return log.sourceType == StagingLog 43 } 44 45 func (log LogMessage) Timestamp() time.Time { 46 return log.timestamp 47 } 48 49 func (log LogMessage) SourceType() string { 50 return log.sourceType 51 } 52 53 func (log LogMessage) SourceInstance() string { 54 return log.sourceInstance 55 } 56 57 func NewLogMessage(message string, messageType string, timestamp time.Time, sourceType string, sourceInstance string) *LogMessage { 58 return &LogMessage{ 59 message: message, 60 messageType: messageType, 61 timestamp: timestamp, 62 sourceType: sourceType, 63 sourceInstance: sourceInstance, 64 } 65 } 66 67 type LogMessages []*LogMessage 68 69 func (lm LogMessages) Len() int { return len(lm) } 70 71 func (lm LogMessages) Less(i, j int) bool { 72 return lm[i].timestamp.Before(lm[j].timestamp) 73 } 74 75 func (lm LogMessages) Swap(i, j int) { 76 lm[i], lm[j] = lm[j], lm[i] 77 } 78 79 type channelWriter struct { 80 errChannel chan error 81 } 82 83 func (c channelWriter) Write(bytes []byte) (n int, err error) { 84 c.errChannel <- errors.New(strings.Trim(string(bytes), "\n")) 85 86 return len(bytes), nil 87 } 88 89 // cliRetryBackoff returns true for OnErr after sleeping the given interval for a limited number of times, 90 // and returns true for OnEmpty always. 91 // Basically: retry x times on connection failures, and wait forever for logs to show up. 92 type cliRetryBackoff struct { 93 interval time.Duration 94 maxCount int 95 count int 96 } 97 98 func newCliRetryBackoff(interval time.Duration, maxCount int) *cliRetryBackoff { 99 return &cliRetryBackoff{ 100 interval: interval, 101 maxCount: maxCount, 102 } 103 } 104 105 func (b *cliRetryBackoff) OnErr(error) bool { 106 b.count++ 107 if b.count >= b.maxCount { 108 return false 109 } 110 111 time.Sleep(b.interval) 112 return true 113 } 114 115 func (b *cliRetryBackoff) OnEmpty() bool { 116 time.Sleep(b.interval) 117 return true 118 } 119 120 func (b *cliRetryBackoff) Reset() { 121 b.count = 0 122 } 123 124 func GetStreamingLogs(appGUID string, client LogCacheClient) (<-chan LogMessage, <-chan error, context.CancelFunc) { 125 126 logrus.Info("Start Tailing Logs") 127 128 outgoingLogStream := make(chan LogMessage, 1000) 129 outgoingErrStream := make(chan error, 1000) 130 ctx, cancelFunc := context.WithCancel(context.Background()) 131 go func() { 132 defer close(outgoingLogStream) 133 defer close(outgoingErrStream) 134 135 ts := latestEnvelopeTimestamp(client, outgoingErrStream, ctx, appGUID) 136 137 // if the context was cancelled we may not have seen an envelope 138 if ts.IsZero() { 139 return 140 } 141 142 const offset = 1 * time.Second 143 walkStartTime := ts.Add(-offset) 144 145 logcache.Walk( 146 ctx, 147 appGUID, 148 logcache.Visitor(func(envelopes []*loggregator_v2.Envelope) bool { 149 logMessages := convertEnvelopesToLogMessages(envelopes) 150 for _, logMessage := range logMessages { 151 select { 152 case <-ctx.Done(): 153 return false 154 default: 155 outgoingLogStream <- *logMessage 156 } 157 } 158 return true 159 }), 160 client.Read, 161 logcache.WithWalkDelay(2*time.Second), 162 logcache.WithWalkStartTime(walkStartTime), 163 logcache.WithWalkEnvelopeTypes(logcache_v1.EnvelopeType_LOG), 164 logcache.WithWalkBackoff(newCliRetryBackoff(retryInterval, retryCount)), 165 logcache.WithWalkLogger(log.New(channelWriter{ 166 errChannel: outgoingErrStream, 167 }, "", 0)), 168 ) 169 }() 170 171 return outgoingLogStream, outgoingErrStream, cancelFunc 172 } 173 174 func latestEnvelopeTimestamp(client LogCacheClient, errs chan error, ctx context.Context, sourceID string) time.Time { 175 176 // Fetching the most recent timestamp could be implemented with client.Read directly rather than using logcache.Walk 177 // We use Walk because we want the extra retry behavior provided through Walk 178 179 // Wrap client.Read in our own function to allow us to specify our own read options 180 // https://github.com/cloudfoundry/go-log-cache/issues/27 181 r := func(ctx context.Context, sourceID string, _ time.Time, opts ...logcache.ReadOption) ([]*loggregator_v2.Envelope, error) { 182 os := []logcache.ReadOption{ 183 logcache.WithLimit(1), 184 logcache.WithDescending(), 185 } 186 for _, o := range opts { 187 os = append(os, o) 188 } 189 return client.Read(ctx, sourceID, time.Time{}, os...) 190 } 191 192 var timestamp time.Time 193 194 logcache.Walk( 195 ctx, 196 sourceID, 197 logcache.Visitor(func(envelopes []*loggregator_v2.Envelope) bool { 198 timestamp = time.Unix(0, envelopes[0].Timestamp) 199 return false 200 }), 201 r, 202 logcache.WithWalkBackoff(newCliRetryBackoff(retryInterval, retryCount)), 203 logcache.WithWalkLogger(log.New(channelWriter{ 204 errChannel: errs, 205 }, "", 0)), 206 ) 207 208 return timestamp 209 } 210 211 func GetRecentLogs(appGUID string, client LogCacheClient) ([]LogMessage, error) { 212 logLineRequestCount := RecentLogsLines 213 var envelopes []*loggregator_v2.Envelope 214 var err error 215 216 for logLineRequestCount >= 1 { 217 envelopes, err = client.Read( 218 context.Background(), 219 appGUID, 220 time.Time{}, 221 logcache.WithEnvelopeTypes(logcache_v1.EnvelopeType_LOG), 222 logcache.WithLimit(logLineRequestCount), 223 logcache.WithDescending(), 224 ) 225 if err == nil || err.Error() != "unexpected status code 429" { 226 break 227 } 228 logLineRequestCount /= 2 229 } 230 if err != nil { 231 return nil, fmt.Errorf("Failed to retrieve logs from Log Cache: %s", err) 232 } 233 234 logMessages := convertEnvelopesToLogMessages(envelopes) 235 var reorderedLogMessages []LogMessage 236 for i := len(logMessages) - 1; i >= 0; i-- { 237 reorderedLogMessages = append(reorderedLogMessages, *logMessages[i]) 238 } 239 240 return reorderedLogMessages, nil 241 } 242 243 func convertEnvelopesToLogMessages(envelopes []*loggregator_v2.Envelope) []*LogMessage { 244 var logMessages []*LogMessage 245 for _, envelope := range envelopes { 246 logEnvelope, ok := envelope.GetMessage().(*loggregator_v2.Envelope_Log) 247 if !ok { 248 continue 249 } 250 log := logEnvelope.Log 251 252 logMessages = append(logMessages, NewLogMessage( 253 string(log.Payload), 254 loggregator_v2.Log_Type_name[int32(log.Type)], 255 time.Unix(0, envelope.GetTimestamp()), 256 envelope.GetTags()["source_type"], 257 envelope.GetInstanceId(), 258 )) 259 } 260 return logMessages 261 }