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  }