github.com/sleungcy-sap/cli@v7.1.0+incompatible/actor/v3action/logging.go (about) 1 package v3action 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 incomingLogStream, incomingErrStream := client.TailingLogs(appGUID, actor.Config.AccessToken()) 81 82 outgoingLogStream, outgoingErrStream := actor.blockOnConnect(ready) 83 84 go actor.streamLogsBetween(incomingLogStream, incomingErrStream, outgoingLogStream, outgoingErrStream) 85 86 return outgoingLogStream, outgoingErrStream 87 } 88 89 func (actor Actor) GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client NOAAClient) (<-chan *LogMessage, <-chan error, Warnings, error) { 90 app, allWarnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID) 91 if err != nil { 92 return nil, nil, allWarnings, err 93 } 94 95 messages, logErrs := actor.GetStreamingLogs(app.GUID, client) 96 97 return messages, logErrs, allWarnings, err 98 } 99 100 func (actor Actor) blockOnConnect(ready <-chan bool) (chan *LogMessage, chan error) { 101 outgoingLogStream := make(chan *LogMessage) 102 outgoingErrStream := make(chan error, 1) 103 104 ticker := time.NewTicker(actor.Config.DialTimeout()) 105 106 dance: 107 for { 108 select { 109 case _, ok := <-ready: 110 if !ok { 111 break dance 112 } 113 case <-ticker.C: 114 outgoingErrStream <- actionerror.NOAATimeoutError{} 115 break dance 116 } 117 } 118 119 return outgoingLogStream, outgoingErrStream 120 } 121 122 func (Actor) flushLogs(logs LogMessages, messages chan<- *LogMessage) LogMessages { 123 sort.Stable(logs) 124 for _, l := range logs { 125 messages <- l 126 } 127 return LogMessages{} 128 } 129 130 func (Actor) setOnConnectBlocker(client NOAAClient) <-chan bool { 131 ready := make(chan bool) 132 var onlyRunOnInitialConnect sync.Once 133 callOnConnectOrRetry := func() { 134 onlyRunOnInitialConnect.Do(func() { 135 close(ready) 136 }) 137 } 138 139 client.SetOnConnectCallback(callOnConnectOrRetry) 140 141 return ready 142 } 143 144 func (actor Actor) streamLogsBetween(incomingLogStream <-chan *events.LogMessage, incomingErrStream <-chan error, outgoingLogStream chan<- *LogMessage, outgoingErrStream chan<- error) { 145 log.Info("Processing Log Stream") 146 147 defer close(outgoingLogStream) 148 defer close(outgoingErrStream) 149 150 ticker := time.NewTicker(flushInterval) 151 defer ticker.Stop() 152 153 var logsToBeSorted LogMessages 154 var eventClosed, errClosed bool 155 156 dance: 157 for { 158 select { 159 case event, ok := <-incomingLogStream: 160 if !ok { 161 if !errClosed { 162 log.Debug("logging event stream closed") 163 } 164 eventClosed = true 165 break 166 } 167 168 logsToBeSorted = append(logsToBeSorted, &LogMessage{ 169 message: string(event.GetMessage()), 170 messageType: event.GetMessageType(), 171 timestamp: time.Unix(0, event.GetTimestamp()), 172 sourceInstance: event.GetSourceInstance(), 173 sourceType: event.GetSourceType(), 174 }) 175 case err, ok := <-incomingErrStream: 176 if !ok { 177 if !errClosed { 178 log.Debug("logging error stream closed") 179 } 180 errClosed = true 181 break 182 } 183 184 if _, ok := err.(noaaErrors.RetryError); ok { 185 break 186 } 187 188 if err != nil { 189 outgoingErrStream <- err 190 } 191 case <-ticker.C: 192 log.Debug("processing logsToBeSorted") 193 logsToBeSorted = actor.flushLogs(logsToBeSorted, outgoingLogStream) 194 if eventClosed && errClosed { 195 log.Debug("stopping log processing") 196 break dance 197 } 198 } 199 } 200 }