github.com/loafoe/cli@v7.1.0+incompatible/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" 12 "code.cloudfoundry.org/go-log-cache/rpc/logcache_v1" 13 "code.cloudfoundry.org/go-loggregator/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 return true 117 } 118 119 func (b *cliRetryBackoff) Reset() { 120 b.count = 0 121 } 122 123 func GetStreamingLogs(appGUID string, client LogCacheClient) (<-chan LogMessage, <-chan error, context.CancelFunc) { 124 125 logrus.Info("Start Tailing Logs") 126 127 outgoingLogStream := make(chan LogMessage, 1000) 128 outgoingErrStream := make(chan error, 1000) 129 ctx, cancelFunc := context.WithCancel(context.Background()) 130 go func() { 131 defer close(outgoingLogStream) 132 defer close(outgoingErrStream) 133 134 ts := latestEnvelopeTimestamp(client, outgoingErrStream, ctx, appGUID) 135 136 // if the context was cancelled we may not have seen an envelope 137 if ts.IsZero() { 138 return 139 } 140 141 const offset = 1 * time.Second 142 walkStartTime := ts.Add(-offset) 143 144 logcache.Walk( 145 ctx, 146 appGUID, 147 logcache.Visitor(func(envelopes []*loggregator_v2.Envelope) bool { 148 logMessages := convertEnvelopesToLogMessages(envelopes) 149 for _, logMessage := range logMessages { 150 select { 151 case <-ctx.Done(): 152 return false 153 default: 154 outgoingLogStream <- *logMessage 155 } 156 } 157 return true 158 }), 159 client.Read, 160 logcache.WithWalkDelay(2*time.Second), 161 logcache.WithWalkStartTime(walkStartTime), 162 logcache.WithWalkEnvelopeTypes(logcache_v1.EnvelopeType_LOG), 163 logcache.WithWalkBackoff(newCliRetryBackoff(retryInterval, retryCount)), 164 logcache.WithWalkLogger(log.New(channelWriter{ 165 errChannel: outgoingErrStream, 166 }, "", 0)), 167 ) 168 }() 169 170 return outgoingLogStream, outgoingErrStream, cancelFunc 171 } 172 173 func latestEnvelopeTimestamp(client LogCacheClient, errs chan error, ctx context.Context, sourceID string) time.Time { 174 175 // Fetching the most recent timestamp could be implemented with client.Read directly rather than using logcache.Walk 176 // We use Walk because we want the extra retry behavior provided through Walk 177 178 // Wrap client.Read in our own function to allow us to specify our own read options 179 // https://github.com/cloudfoundry/go-log-cache/issues/27 180 r := func(ctx context.Context, sourceID string, _ time.Time, opts ...logcache.ReadOption) ([]*loggregator_v2.Envelope, error) { 181 os := []logcache.ReadOption{ 182 logcache.WithLimit(1), 183 logcache.WithDescending(), 184 } 185 for _, o := range opts { 186 os = append(os, o) 187 } 188 return client.Read(ctx, sourceID, time.Time{}, os...) 189 } 190 191 var timestamp time.Time 192 193 logcache.Walk( 194 ctx, 195 sourceID, 196 logcache.Visitor(func(envelopes []*loggregator_v2.Envelope) bool { 197 timestamp = time.Unix(0, envelopes[0].Timestamp) 198 return false 199 }), 200 r, 201 logcache.WithWalkBackoff(newCliRetryBackoff(retryInterval, retryCount)), 202 logcache.WithWalkLogger(log.New(channelWriter{ 203 errChannel: errs, 204 }, "", 0)), 205 ) 206 207 return timestamp 208 } 209 210 func GetRecentLogs(appGUID string, client LogCacheClient) ([]LogMessage, error) { 211 logLineRequestCount := RecentLogsLines 212 var envelopes []*loggregator_v2.Envelope 213 var err error 214 215 for logLineRequestCount >= 1 { 216 envelopes, err = client.Read( 217 context.Background(), 218 appGUID, 219 time.Time{}, 220 logcache.WithEnvelopeTypes(logcache_v1.EnvelopeType_LOG), 221 logcache.WithLimit(logLineRequestCount), 222 logcache.WithDescending(), 223 ) 224 if err == nil || err.Error() != "unexpected status code 429" { 225 break 226 } 227 logLineRequestCount /= 2 228 } 229 if err != nil { 230 return nil, fmt.Errorf("Failed to retrieve logs from Log Cache: %s", err) 231 } 232 233 logMessages := convertEnvelopesToLogMessages(envelopes) 234 var reorderedLogMessages []LogMessage 235 for i := len(logMessages) - 1; i >= 0; i-- { 236 reorderedLogMessages = append(reorderedLogMessages, *logMessages[i]) 237 } 238 239 return reorderedLogMessages, nil 240 } 241 242 func convertEnvelopesToLogMessages(envelopes []*loggregator_v2.Envelope) []*LogMessage { 243 var logMessages []*LogMessage 244 for _, envelope := range envelopes { 245 logEnvelope, ok := envelope.GetMessage().(*loggregator_v2.Envelope_Log) 246 if !ok { 247 continue 248 } 249 log := logEnvelope.Log 250 251 logMessages = append(logMessages, NewLogMessage( 252 string(log.Payload), 253 loggregator_v2.Log_Type_name[int32(log.Type)], 254 time.Unix(0, envelope.GetTimestamp()), 255 envelope.GetTags()["source_type"], 256 envelope.GetInstanceId(), 257 )) 258 } 259 return logMessages 260 }