github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/runtime/kubernetes/logs.go (about)

     1  // Licensed under the Apache License, Version 2.0 (the "License");
     2  // you may not use this file except in compliance with the License.
     3  // You may obtain a copy of the License at
     4  //
     5  //     https://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS,
     9  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  // See the License for the specific language governing permissions and
    11  // limitations under the License.
    12  //
    13  // Original source: github.com/micro/go-micro/v3/runtime/kubernetes/logs.go
    14  
    15  package kubernetes
    16  
    17  import (
    18  	"bufio"
    19  	"strconv"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/tickoalcantara12/micro/v3/service/errors"
    24  	"github.com/tickoalcantara12/micro/v3/service/logger"
    25  	"github.com/tickoalcantara12/micro/v3/service/runtime"
    26  	"github.com/tickoalcantara12/micro/v3/service/runtime/kubernetes/client"
    27  )
    28  
    29  type klog struct {
    30  	client      client.Client
    31  	serviceName string
    32  	version     string
    33  	options     runtime.LogsOptions
    34  }
    35  
    36  func (k *klog) podLogs(podName string, stream *kubeStream) error {
    37  	p := make(map[string]string)
    38  	p["follow"] = "true"
    39  
    40  	opts := []client.LogOption{
    41  		client.LogParams(p),
    42  		client.LogNamespace(k.options.Namespace),
    43  	}
    44  
    45  	// get the logs for the pod
    46  	body, err := k.client.Log(&client.Resource{
    47  		Name: podName,
    48  		Kind: "pod",
    49  	}, opts...)
    50  
    51  	if err != nil {
    52  		stream.err = err
    53  		stream.Stop()
    54  		return err
    55  	}
    56  
    57  	s := bufio.NewScanner(body)
    58  	defer body.Close()
    59  
    60  	for {
    61  		select {
    62  		case <-stream.stop:
    63  			return stream.Error()
    64  		default:
    65  			if s.Scan() {
    66  				record := runtime.Log{
    67  					Message: s.Text(),
    68  				}
    69  
    70  				// send the records to the stream
    71  				// there can be multiple pods doing this
    72  				select {
    73  				case stream.stream <- record:
    74  				case <-stream.stop:
    75  					return stream.Error()
    76  				}
    77  			} else {
    78  				// TODO: is there a blocking call
    79  				// rather than a sleep loop?
    80  				time.Sleep(time.Second)
    81  			}
    82  		}
    83  	}
    84  }
    85  
    86  func (k *klog) getMatchingPods() ([]string, error) {
    87  	r := &client.Resource{
    88  		Kind:  "pod",
    89  		Value: new(client.PodList),
    90  	}
    91  
    92  	l := make(map[string]string)
    93  
    94  	l["name"] = client.Format(k.serviceName)
    95  
    96  	if len(k.version) > 0 {
    97  		l["version"] = client.Format(k.version)
    98  	}
    99  
   100  	// TODO: specify micro:service
   101  	// l["micro"] = "service"
   102  
   103  	opts := []client.GetOption{
   104  		client.GetLabels(l),
   105  		client.GetNamespace(k.options.Namespace),
   106  	}
   107  
   108  	if err := k.client.Get(r, opts...); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	var matches []string
   113  
   114  	for _, p := range r.Value.(*client.PodList).Items {
   115  		// find labels that match the name
   116  		if p.Metadata.Labels["name"] == client.Format(k.serviceName) {
   117  			matches = append(matches, p.Metadata.Name)
   118  		}
   119  	}
   120  
   121  	return matches, nil
   122  }
   123  
   124  func (k *klog) Read() ([]runtime.Log, error) {
   125  	pods, err := k.getMatchingPods()
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	if len(pods) == 0 {
   130  		return nil, errors.NotFound("runtime.logs", "no such service")
   131  	}
   132  
   133  	var records []runtime.Log
   134  
   135  	for _, pod := range pods {
   136  		logParams := make(map[string]string)
   137  
   138  		//if !opts.Since.Equal(time.Time{}) {
   139  		//	logParams["sinceSeconds"] = strconv.Itoa(int(time.Since(opts.Since).Seconds()))
   140  		//}
   141  
   142  		if k.options.Count != 0 {
   143  			logParams["tailLines"] = strconv.Itoa(int(k.options.Count))
   144  		}
   145  
   146  		if k.options.Stream == true {
   147  			logParams["follow"] = "true"
   148  		}
   149  
   150  		opts := []client.LogOption{
   151  			client.LogParams(logParams),
   152  			client.LogNamespace(k.options.Namespace),
   153  		}
   154  
   155  		logs, err := k.client.Log(&client.Resource{
   156  			Name: pod,
   157  			Kind: "pod",
   158  		}, opts...)
   159  
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  		defer logs.Close()
   164  
   165  		s := bufio.NewScanner(logs)
   166  
   167  		for s.Scan() {
   168  			record := runtime.Log{
   169  				Message: s.Text(),
   170  			}
   171  			// record.Metadata["pod"] = pod
   172  			records = append(records, record)
   173  		}
   174  	}
   175  
   176  	// sort the records
   177  	// sort.Slice(records, func(i, j int) bool { return records[i].Timestamp.Before(records[j].Timestamp) })
   178  
   179  	return records, nil
   180  }
   181  
   182  func (k *klog) Stream() (runtime.LogStream, error) {
   183  	// find the matching pods
   184  	pods, err := k.getMatchingPods()
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	if len(pods) == 0 {
   190  		return nil, errors.NotFound("runtime.logs", "no such service")
   191  	}
   192  
   193  	stream := &kubeStream{
   194  		stream: make(chan runtime.Log),
   195  		stop:   make(chan bool),
   196  	}
   197  
   198  	var wg sync.WaitGroup
   199  
   200  	// stream from the individual pods
   201  	for _, pod := range pods {
   202  		wg.Add(1)
   203  
   204  		go func(podName string) {
   205  			if err := k.podLogs(podName, stream); err != nil {
   206  				logger.Errorf("Error streaming from pod: %v", err)
   207  			}
   208  
   209  			wg.Done()
   210  		}(pod)
   211  	}
   212  
   213  	go func() {
   214  		// wait until all pod log watchers are done
   215  		wg.Wait()
   216  
   217  		// do any cleanup
   218  		stream.Stop()
   219  
   220  		// close the stream
   221  		close(stream.stream)
   222  	}()
   223  
   224  	return stream, nil
   225  }
   226  
   227  // NewLog returns a configured Kubernetes logger
   228  func newLog(c client.Client, serviceName, version string, opts ...runtime.LogsOption) *klog {
   229  	options := runtime.LogsOptions{
   230  		Namespace: client.DefaultNamespace,
   231  	}
   232  	for _, o := range opts {
   233  		o(&options)
   234  	}
   235  
   236  	klog := &klog{
   237  		serviceName: serviceName,
   238  		version:     version,
   239  		client:      c,
   240  		options:     options,
   241  	}
   242  
   243  	return klog
   244  }