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  }