github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+incompatible/actor/v2action/logging.go (about) 1 package v2action 2 3 import ( 4 "sort" 5 "sync" 6 "time" 7 8 "code.cloudfoundry.org/cli/actor/actionerror" 9 "github.com/cloudfoundry/noaa" 10 noaaErrors "github.com/cloudfoundry/noaa/errors" 11 "github.com/cloudfoundry/sonde-go/events" 12 log "github.com/sirupsen/logrus" 13 ) 14 15 const StagingLog = "STG" 16 17 var flushInterval = 300 * time.Millisecond 18 19 type LogMessage struct { 20 message string 21 messageType events.LogMessage_MessageType 22 timestamp time.Time 23 sourceType string 24 sourceInstance string 25 } 26 27 func (log LogMessage) Message() string { 28 return log.message 29 } 30 31 func (log LogMessage) Type() string { 32 if log.messageType == events.LogMessage_OUT { 33 return "OUT" 34 } 35 return "ERR" 36 } 37 38 func (log LogMessage) Staging() bool { 39 return log.sourceType == StagingLog 40 } 41 42 func (log LogMessage) Timestamp() time.Time { 43 return log.timestamp 44 } 45 46 func (log LogMessage) SourceType() string { 47 return log.sourceType 48 } 49 50 func (log LogMessage) SourceInstance() string { 51 return log.sourceInstance 52 } 53 54 func NewLogMessage(message string, messageType int, timestamp time.Time, sourceType string, sourceInstance string) *LogMessage { 55 return &LogMessage{ 56 message: message, 57 messageType: events.LogMessage_MessageType(messageType), 58 timestamp: timestamp, 59 sourceType: sourceType, 60 sourceInstance: sourceInstance, 61 } 62 } 63 64 type LogMessages []*LogMessage 65 66 func (lm LogMessages) Len() int { return len(lm) } 67 68 func (lm LogMessages) Less(i, j int) bool { 69 return lm[i].timestamp.Before(lm[j].timestamp) 70 } 71 72 func (lm LogMessages) Swap(i, j int) { 73 lm[i], lm[j] = lm[j], lm[i] 74 } 75 76 func (actor Actor) GetStreamingLogs(appGUID string, client NOAAClient) (<-chan *LogMessage, <-chan error) { 77 log.Info("Start Tailing Logs") 78 79 ready := actor.setOnConnectBlocker(client) 80 81 // Do not pass in token because client should have a TokenRefresher set 82 incomingLogStream, incomingErrStream := client.TailingLogs(appGUID, "") 83 84 outgoingLogStream, outgoingErrStream := actor.blockOnConnect(ready) 85 86 go actor.streamLogsBetween(incomingLogStream, incomingErrStream, outgoingLogStream, outgoingErrStream) 87 88 return outgoingLogStream, outgoingErrStream 89 } 90 91 func (actor Actor) GetRecentLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client NOAAClient) ([]LogMessage, Warnings, error) { 92 app, allWarnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID) 93 if err != nil { 94 return nil, allWarnings, err 95 } 96 97 noaaMessages, err := client.RecentLogs(app.GUID, "") 98 if err != nil { 99 return nil, allWarnings, err 100 } 101 102 noaaMessages = noaa.SortRecent(noaaMessages) 103 104 var logMessages []LogMessage 105 106 for _, message := range noaaMessages { 107 logMessages = append(logMessages, LogMessage{ 108 message: string(message.GetMessage()), 109 messageType: message.GetMessageType(), 110 timestamp: time.Unix(0, message.GetTimestamp()), 111 sourceType: message.GetSourceType(), 112 sourceInstance: message.GetSourceInstance(), 113 }) 114 } 115 116 return logMessages, allWarnings, nil 117 } 118 119 func (actor Actor) GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client NOAAClient) (<-chan *LogMessage, <-chan error, Warnings, error) { 120 app, allWarnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID) 121 if err != nil { 122 return nil, nil, allWarnings, err 123 } 124 125 messages, logErrs := actor.GetStreamingLogs(app.GUID, client) 126 127 return messages, logErrs, allWarnings, err 128 } 129 130 func (actor Actor) blockOnConnect(ready <-chan bool) (chan *LogMessage, chan error) { 131 outgoingLogStream := make(chan *LogMessage) 132 outgoingErrStream := make(chan error, 1) 133 134 ticker := time.NewTicker(actor.Config.DialTimeout()) 135 136 dance: 137 for { 138 select { 139 case _, ok := <-ready: 140 if !ok { 141 break dance 142 } 143 case <-ticker.C: 144 outgoingErrStream <- actionerror.NOAATimeoutError{} 145 break dance 146 } 147 } 148 149 return outgoingLogStream, outgoingErrStream 150 } 151 152 func (Actor) flushLogs(logs LogMessages, outgoingLogStream chan<- *LogMessage) LogMessages { 153 sort.Stable(logs) 154 for _, l := range logs { 155 outgoingLogStream <- l 156 } 157 return LogMessages{} 158 } 159 160 func (Actor) setOnConnectBlocker(client NOAAClient) <-chan bool { 161 ready := make(chan bool) 162 var onlyRunOnInitialConnect sync.Once 163 callOnConnectOrRetry := func() { 164 onlyRunOnInitialConnect.Do(func() { 165 close(ready) 166 }) 167 } 168 169 client.SetOnConnectCallback(callOnConnectOrRetry) 170 171 return ready 172 } 173 174 func (actor Actor) streamLogsBetween(incomingLogStream <-chan *events.LogMessage, incomingErrStream <-chan error, outgoingLogStream chan<- *LogMessage, outgoingErrStream chan<- error) { 175 log.Info("Processing Log Stream") 176 177 defer close(outgoingLogStream) 178 defer close(outgoingErrStream) 179 180 ticker := time.NewTicker(flushInterval) 181 defer ticker.Stop() 182 183 var logsToBeSorted LogMessages 184 var eventClosed, errClosed bool 185 186 dance: 187 for { 188 select { 189 case event, ok := <-incomingLogStream: 190 if !ok { 191 if !errClosed { 192 log.Debug("logging event stream closed") 193 } 194 eventClosed = true 195 break 196 } 197 198 logsToBeSorted = append(logsToBeSorted, &LogMessage{ 199 message: string(event.GetMessage()), 200 messageType: event.GetMessageType(), 201 timestamp: time.Unix(0, event.GetTimestamp()), 202 sourceInstance: event.GetSourceInstance(), 203 sourceType: event.GetSourceType(), 204 }) 205 case err, ok := <-incomingErrStream: 206 if !ok { 207 if !errClosed { 208 log.Debug("logging error stream closed") 209 } 210 errClosed = true 211 break 212 } 213 214 if _, ok := err.(noaaErrors.RetryError); ok { 215 break 216 } 217 218 if err != nil { 219 outgoingErrStream <- err 220 } 221 case <-ticker.C: 222 log.Debug("processing logsToBeSorted") 223 logsToBeSorted = actor.flushLogs(logsToBeSorted, outgoingLogStream) 224 if eventClosed && errClosed { 225 log.Debug("stopping log processing") 226 break dance 227 } 228 } 229 } 230 }