github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+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  }