github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+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  	incomingLogStream, incomingErrStream := client.TailingLogs(appGUID, actor.Config.AccessToken())
    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) GetRecentLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client NOAAClient) ([]LogMessage, Warnings, error) {
    91  	app, allWarnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID)
    92  	if err != nil {
    93  		return nil, allWarnings, err
    94  	}
    95  
    96  	noaaMessages, err := client.RecentLogs(app.GUID, actor.Config.AccessToken())
    97  	if err != nil {
    98  		return nil, allWarnings, err
    99  	}
   100  
   101  	noaaMessages = noaa.SortRecent(noaaMessages)
   102  
   103  	var logMessages []LogMessage
   104  
   105  	for _, message := range noaaMessages {
   106  		logMessages = append(logMessages, LogMessage{
   107  			message:        string(message.GetMessage()),
   108  			messageType:    message.GetMessageType(),
   109  			timestamp:      time.Unix(0, message.GetTimestamp()),
   110  			sourceType:     message.GetSourceType(),
   111  			sourceInstance: message.GetSourceInstance(),
   112  		})
   113  	}
   114  
   115  	return logMessages, allWarnings, nil
   116  }
   117  
   118  func (actor Actor) GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client NOAAClient) (<-chan *LogMessage, <-chan error, Warnings, error) {
   119  	app, allWarnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID)
   120  	if err != nil {
   121  		return nil, nil, allWarnings, err
   122  	}
   123  
   124  	messages, logErrs := actor.GetStreamingLogs(app.GUID, client)
   125  
   126  	return messages, logErrs, allWarnings, err
   127  }
   128  
   129  func (actor Actor) blockOnConnect(ready <-chan bool) (chan *LogMessage, chan error) {
   130  	outgoingLogStream := make(chan *LogMessage)
   131  	outgoingErrStream := make(chan error, 1)
   132  
   133  	ticker := time.NewTicker(actor.Config.DialTimeout())
   134  
   135  dance:
   136  	for {
   137  		select {
   138  		case _, ok := <-ready:
   139  			if !ok {
   140  				break dance
   141  			}
   142  		case <-ticker.C:
   143  			outgoingErrStream <- actionerror.NOAATimeoutError{}
   144  			break dance
   145  		}
   146  	}
   147  
   148  	return outgoingLogStream, outgoingErrStream
   149  }
   150  
   151  func (Actor) flushLogs(logs LogMessages, outgoingLogStream chan<- *LogMessage) LogMessages {
   152  	sort.Stable(logs)
   153  	for _, l := range logs {
   154  		outgoingLogStream <- l
   155  	}
   156  	return LogMessages{}
   157  }
   158  
   159  func (Actor) setOnConnectBlocker(client NOAAClient) <-chan bool {
   160  	ready := make(chan bool)
   161  	var onlyRunOnInitialConnect sync.Once
   162  	callOnConnectOrRetry := func() {
   163  		onlyRunOnInitialConnect.Do(func() {
   164  			close(ready)
   165  		})
   166  	}
   167  
   168  	client.SetOnConnectCallback(callOnConnectOrRetry)
   169  
   170  	return ready
   171  }
   172  
   173  func (actor Actor) streamLogsBetween(incomingLogStream <-chan *events.LogMessage, incomingErrStream <-chan error, outgoingLogStream chan<- *LogMessage, outgoingErrStream chan<- error) {
   174  	log.Info("Processing Log Stream")
   175  
   176  	defer close(outgoingLogStream)
   177  	defer close(outgoingErrStream)
   178  
   179  	ticker := time.NewTicker(flushInterval)
   180  	defer ticker.Stop()
   181  
   182  	var logsToBeSorted LogMessages
   183  	var eventClosed, errClosed bool
   184  
   185  dance:
   186  	for {
   187  		select {
   188  		case event, ok := <-incomingLogStream:
   189  			if !ok {
   190  				if !errClosed {
   191  					log.Debug("logging event stream closed")
   192  				}
   193  				eventClosed = true
   194  				break
   195  			}
   196  
   197  			logsToBeSorted = append(logsToBeSorted, &LogMessage{
   198  				message:        string(event.GetMessage()),
   199  				messageType:    event.GetMessageType(),
   200  				timestamp:      time.Unix(0, event.GetTimestamp()),
   201  				sourceInstance: event.GetSourceInstance(),
   202  				sourceType:     event.GetSourceType(),
   203  			})
   204  		case err, ok := <-incomingErrStream:
   205  			if !ok {
   206  				if !errClosed {
   207  					log.Debug("logging error stream closed")
   208  				}
   209  				errClosed = true
   210  				break
   211  			}
   212  
   213  			if _, ok := err.(noaaErrors.RetryError); ok {
   214  				break
   215  			}
   216  
   217  			if err != nil {
   218  				outgoingErrStream <- err
   219  			}
   220  		case <-ticker.C:
   221  			log.Debug("processing logsToBeSorted")
   222  			logsToBeSorted = actor.flushLogs(logsToBeSorted, outgoingLogStream)
   223  			if eventClosed && errClosed {
   224  				log.Debug("stopping log processing")
   225  				break dance
   226  			}
   227  		}
   228  	}
   229  }