...

Source file src/github.com/redhat-developer/odo/pkg/machineoutput/event_logging.go

Documentation: github.com/redhat-developer/odo/pkg/machineoutput

     1  package machineoutput
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"time"
     9  
    10  	"github.com/redhat-developer/odo/pkg/log"
    11  
    12  	"k8s.io/klog"
    13  )
    14  
    15  // FormatTime returns time in UTC Unix Epoch Seconds and then the microsecond portion of that time.
    16  func FormatTime(time time.Time) string {
    17  	result := fmt.Sprintf("%d.%06d", time.Unix(), time.Nanosecond()/1000)
    18  	return result
    19  
    20  }
    21  
    22  // TimestampNow returns timestamp in format of (seconds since UTC Unix epoch).(microseconds time component)
    23  func TimestampNow() string {
    24  	return FormatTime(time.Now())
    25  }
    26  
    27  // NewMachineEventLoggingClient creates the appropriate client based on whether we are in machine logging mode or not
    28  func NewMachineEventLoggingClient() MachineEventLoggingClient {
    29  	if log.IsJSON() {
    30  		return NewConsoleMachineEventLoggingClient()
    31  	}
    32  
    33  	return NewNoOpMachineEventLoggingClient()
    34  }
    35  
    36  // NewNoOpMachineEventLoggingClient creates a new instance of NoOpMachineEventLoggingClient,
    37  // which will ignore any provided events.
    38  func NewNoOpMachineEventLoggingClient() *NoOpMachineEventLoggingClient {
    39  	return &NoOpMachineEventLoggingClient{}
    40  }
    41  
    42  var _ MachineEventLoggingClient = &NoOpMachineEventLoggingClient{}
    43  
    44  // DevFileCommandExecutionBegin ignores the provided event.
    45  func (c *NoOpMachineEventLoggingClient) DevFileCommandExecutionBegin(commandID string, componentName string, commandLine string, groupKind string, timestamp string) {
    46  }
    47  
    48  // DevFileCommandExecutionComplete ignores the provided event.
    49  func (c *NoOpMachineEventLoggingClient) DevFileCommandExecutionComplete(commandID string, componentName string, commandLine string, groupKind string, timestamp string, errorVal error) {
    50  }
    51  
    52  // CreateContainerOutputWriter ignores the provided event.
    53  func (c *NoOpMachineEventLoggingClient) CreateContainerOutputWriter() (*io.PipeWriter, chan interface{}, *io.PipeWriter, chan interface{}) {
    54  
    55  	channels := []chan interface{}{make(chan interface{}), make(chan interface{})}
    56  
    57  	// Ensure there is always a result waiting on each of the channels
    58  	for _, channelPtr := range channels {
    59  		channelVal := channelPtr
    60  
    61  		go func(channel chan interface{}) {
    62  			for {
    63  				channel <- nil
    64  			}
    65  		}(channelVal)
    66  	}
    67  
    68  	return nil, channels[0], nil, channels[1]
    69  }
    70  
    71  // ReportError ignores the provided event.
    72  func (c *NoOpMachineEventLoggingClient) ReportError(errorVal error, timestamp string) {}
    73  
    74  // ContainerStatus ignores the provided event.
    75  func (c *NoOpMachineEventLoggingClient) ContainerStatus(statuses []ContainerStatusEntry, timestamp string) {
    76  }
    77  
    78  // URLReachable ignores the provided event.
    79  func (c *NoOpMachineEventLoggingClient) URLReachable(name string, url string, port int, secure bool, kind string, reachable bool, timestamp string) {
    80  
    81  }
    82  
    83  // KubernetesPodStatus ignores the provided event.
    84  func (c *NoOpMachineEventLoggingClient) KubernetesPodStatus(pods []KubernetesPodStatusEntry, timestamp string) {
    85  
    86  }
    87  
    88  // NewConsoleMachineEventLoggingClient creates a new instance of ConsoleMachineEventLoggingClient,
    89  // which will output events as JSON to the console.
    90  func NewConsoleMachineEventLoggingClient() *ConsoleMachineEventLoggingClient {
    91  	return &ConsoleMachineEventLoggingClient{}
    92  }
    93  
    94  var _ MachineEventLoggingClient = &ConsoleMachineEventLoggingClient{}
    95  
    96  // DevFileCommandExecutionBegin outputs the provided event as JSON to the console.
    97  func (c *ConsoleMachineEventLoggingClient) DevFileCommandExecutionBegin(commandID string, componentName string, commandLine string, groupKind string, timestamp string) {
    98  
    99  	json := MachineEventWrapper{
   100  		DevFileCommandExecutionBegin: &DevFileCommandExecutionBegin{
   101  			CommandID:        commandID,
   102  			ComponentName:    componentName,
   103  			CommandLine:      commandLine,
   104  			GroupKind:        groupKind,
   105  			AbstractLogEvent: AbstractLogEvent{Timestamp: timestamp},
   106  		},
   107  	}
   108  
   109  	c.outputJSON(json)
   110  }
   111  
   112  // DevFileCommandExecutionComplete outputs the provided event as JSON to the console.
   113  func (c *ConsoleMachineEventLoggingClient) DevFileCommandExecutionComplete(commandID string, componentName string, commandLine string, groupKind string, timestamp string, errorVal error) {
   114  
   115  	errorStr := ""
   116  
   117  	if errorVal != nil {
   118  		errorStr = errorVal.Error()
   119  	}
   120  
   121  	json := MachineEventWrapper{
   122  		DevFileCommandExecutionComplete: &DevFileCommandExecutionComplete{
   123  			DevFileCommandExecutionBegin: DevFileCommandExecutionBegin{
   124  				CommandID:        commandID,
   125  				ComponentName:    componentName,
   126  				CommandLine:      commandLine,
   127  				GroupKind:        groupKind,
   128  				AbstractLogEvent: AbstractLogEvent{Timestamp: timestamp},
   129  			},
   130  			Error: errorStr,
   131  		},
   132  	}
   133  
   134  	c.outputJSON(json)
   135  }
   136  
   137  // CreateContainerOutputWriter returns an io.PipeWriter for which the devfile command/action process output should be
   138  // written (for example by passing the io.PipeWriter to exec.ExecuteCommand), and a channel for communicating when the last data
   139  // has been received on the reader.
   140  //
   141  // All text written to the returned object will be output as a log text event.
   142  // Returned channels will each contain a single nil entry once the underlying reader has closed.
   143  func (c *ConsoleMachineEventLoggingClient) CreateContainerOutputWriter() (*io.PipeWriter, chan interface{}, *io.PipeWriter, chan interface{}) {
   144  
   145  	stdoutWriter, stdoutChannel := createWriterAndChannel(false)
   146  	stderrWriter, stderrChannel := createWriterAndChannel(true)
   147  
   148  	return stdoutWriter, stdoutChannel, stderrWriter, stderrChannel
   149  
   150  }
   151  
   152  // ReportError outputs the provided event as JSON to the console.
   153  func (c *ConsoleMachineEventLoggingClient) ReportError(errorVal error, timestamp string) {
   154  	json := MachineEventWrapper{
   155  		ReportError: &ReportError{
   156  			Error:            errorVal.Error(),
   157  			AbstractLogEvent: AbstractLogEvent{Timestamp: timestamp},
   158  		},
   159  	}
   160  
   161  	c.outputJSON(json)
   162  }
   163  
   164  // ContainerStatus outputs the provided event as JSON to the console.
   165  func (c *ConsoleMachineEventLoggingClient) ContainerStatus(statuses []ContainerStatusEntry, timestamp string) {
   166  	json := MachineEventWrapper{
   167  		ContainerStatus: &ContainerStatus{
   168  			Status: statuses,
   169  			AbstractLogEvent: AbstractLogEvent{
   170  				Timestamp: timestamp,
   171  			},
   172  		},
   173  	}
   174  	c.outputJSON(json)
   175  }
   176  
   177  // URLReachable outputs the provided event as JSON to the console.
   178  func (c *ConsoleMachineEventLoggingClient) URLReachable(name string, url string, port int, secure bool, kind string, reachable bool, timestamp string) {
   179  	json := MachineEventWrapper{
   180  		URLReachable: &URLReachable{
   181  			Name:             name,
   182  			URL:              url,
   183  			Port:             port,
   184  			Secure:           secure,
   185  			Kind:             kind,
   186  			Reachable:        reachable,
   187  			AbstractLogEvent: AbstractLogEvent{Timestamp: timestamp},
   188  		},
   189  	}
   190  	c.outputJSON(json)
   191  }
   192  
   193  // KubernetesPodStatus outputs the provided event as JSON to the console.
   194  func (c *ConsoleMachineEventLoggingClient) KubernetesPodStatus(pods []KubernetesPodStatusEntry, timestamp string) {
   195  	json := MachineEventWrapper{
   196  		KubernetesPodStatus: &KubernetesPodStatus{
   197  			Pods: pods,
   198  			AbstractLogEvent: AbstractLogEvent{
   199  				Timestamp: timestamp,
   200  			},
   201  		},
   202  	}
   203  	c.outputJSON(json)
   204  }
   205  
   206  func (c *ConsoleMachineEventLoggingClient) outputJSON(machineOutput MachineEventWrapper) {
   207  
   208  	if c.logFunc != nil {
   209  		c.logFunc(machineOutput)
   210  		return
   211  	}
   212  
   213  	OutputSuccessUnindented(machineOutput)
   214  }
   215  
   216  // GetEntry will return the JSON event parsed from a single line of '-o json' machine readable console output.
   217  // Currently used for test purposes only.
   218  func (w MachineEventWrapper) GetEntry() (MachineEventLogEntry, error) {
   219  	if w.DevFileCommandExecutionBegin != nil {
   220  		return w.DevFileCommandExecutionBegin, nil
   221  	}
   222  
   223  	if w.DevFileCommandExecutionComplete != nil {
   224  		return w.DevFileCommandExecutionComplete, nil
   225  	}
   226  
   227  	if w.LogText != nil {
   228  		return w.LogText, nil
   229  	}
   230  
   231  	if w.ReportError != nil {
   232  		return w.ReportError, nil
   233  	}
   234  
   235  	if w.KubernetesPodStatus != nil {
   236  		return w.KubernetesPodStatus, nil
   237  	}
   238  
   239  	if w.ContainerStatus != nil {
   240  		return w.ContainerStatus, nil
   241  	}
   242  
   243  	if w.URLReachable != nil {
   244  		return w.URLReachable, nil
   245  	}
   246  
   247  	return nil, errors.New("unexpected machine event log entry")
   248  }
   249  
   250  // GetTimestamp returns the timestamp element for this event.
   251  func (c AbstractLogEvent) GetTimestamp() string { return c.Timestamp }
   252  
   253  // GetType returns the event type for this event.
   254  func (c DevFileCommandExecutionBegin) GetType() MachineEventLogEntryType {
   255  	return TypeDevFileCommandExecutionBegin
   256  }
   257  
   258  // GetType returns the event type for this event.
   259  func (c DevFileCommandExecutionComplete) GetType() MachineEventLogEntryType {
   260  	return TypeDevFileCommandExecutionComplete
   261  }
   262  
   263  // GetType returns the event type for this event.
   264  func (c LogText) GetType() MachineEventLogEntryType { return TypeLogText }
   265  
   266  // GetType returns the event type for this event.
   267  func (c ReportError) GetType() MachineEventLogEntryType { return TypeReportError }
   268  
   269  // GetType returns the event type for this event.
   270  func (c ContainerStatus) GetType() MachineEventLogEntryType { return TypeContainerStatus }
   271  
   272  // GetType returns the event type for this event.
   273  func (c URLReachable) GetType() MachineEventLogEntryType { return TypeURLReachable }
   274  
   275  // GetType returns the event type for this event.
   276  func (c KubernetesPodStatus) GetType() MachineEventLogEntryType { return TypeKubernetesPodStatus }
   277  
   278  // MachineEventLogEntryType indicates the machine-readable event type from an ODO operation
   279  type MachineEventLogEntryType int
   280  
   281  const (
   282  	// TypeDevFileCommandExecutionBegin is the entry type for that event.
   283  	TypeDevFileCommandExecutionBegin MachineEventLogEntryType = 0
   284  	// TypeDevFileCommandExecutionComplete is the entry type for that event.
   285  	TypeDevFileCommandExecutionComplete MachineEventLogEntryType = 1
   286  	// TypeLogText is the entry type for that event.
   287  	TypeLogText MachineEventLogEntryType = 2
   288  	// TypeReportError is the entry type for that event.
   289  	TypeReportError MachineEventLogEntryType = 3
   290  	// TypeContainerStatus is the entry type for that event.
   291  	TypeContainerStatus MachineEventLogEntryType = 5
   292  	// TypeURLReachable is the entry type for that event.
   293  	TypeURLReachable MachineEventLogEntryType = 6
   294  	// TypeKubernetesPodStatus is the entry type for that event.
   295  	TypeKubernetesPodStatus MachineEventLogEntryType = 7
   296  )
   297  
   298  // createWriterAndChannel is similar to the exec.CreateConsoleOutputWriterAndChannel(); see that function's comment for details.
   299  func createWriterAndChannel(stderr bool) (*io.PipeWriter, chan interface{}) {
   300  	reader, writer := io.Pipe()
   301  
   302  	closeChannel := make(chan interface{})
   303  
   304  	stream := "stdout"
   305  	if stderr {
   306  		stream = "stderr"
   307  	}
   308  
   309  	go func() {
   310  
   311  		bufReader := bufio.NewReader(reader)
   312  		for {
   313  			line, _, err := bufReader.ReadLine()
   314  			if err != nil {
   315  				if err != io.EOF {
   316  					klog.V(4).Infof("Unexpected error on reading container output reader: %v", err)
   317  				}
   318  				break
   319  			}
   320  
   321  			// Output log text event for each line we receive
   322  			json := MachineEventWrapper{
   323  				LogText: &LogText{
   324  					AbstractLogEvent: AbstractLogEvent{Timestamp: TimestampNow()},
   325  					Text:             string(line),
   326  					Stream:           stream,
   327  				},
   328  			}
   329  			OutputSuccessUnindented(json)
   330  		}
   331  
   332  		// Output a single nil event on the channel to inform that the last line of text has been
   333  		// received from the writer.
   334  		closeChannel <- nil
   335  	}()
   336  
   337  	return writer, closeChannel
   338  }
   339  

View as plain text