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