github.com/annwntech/go-micro/v2@v2.9.5/runtime/kubernetes/logs.go (about)

     1  // Package kubernetes taken from https://github.com/micro/go-micro/blob/master/debug/log/kubernetes/kubernetes.go
     2  // There are some modifications compared to the other files as
     3  // this package doesn't provide write functionality.
     4  // With the write functionality gone, structured logs also go away.
     5  package kubernetes
     6  
     7  import (
     8  	"bufio"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/annwntech/go-micro/v2/runtime"
    13  	"github.com/annwntech/go-micro/v2/util/kubernetes/client"
    14  	"github.com/annwntech/go-micro/v2/util/log"
    15  )
    16  
    17  type klog struct {
    18  	client      client.Client
    19  	serviceName string
    20  	options     runtime.LogsOptions
    21  }
    22  
    23  func (k *klog) podLogStream(podName string, stream *kubeStream) error {
    24  	p := make(map[string]string)
    25  	p["follow"] = "true"
    26  
    27  	opts := []client.LogOption{
    28  		client.LogParams(p),
    29  		client.LogNamespace(k.options.Namespace),
    30  	}
    31  
    32  	// get the logs for the pod
    33  	body, err := k.client.Log(&client.Resource{
    34  		Name: podName,
    35  		Kind: "pod",
    36  	}, opts...)
    37  
    38  	if err != nil {
    39  		stream.err = err
    40  		stream.Stop()
    41  		return err
    42  	}
    43  
    44  	s := bufio.NewScanner(body)
    45  	defer body.Close()
    46  
    47  	for {
    48  		select {
    49  		case <-stream.stop:
    50  			return stream.Error()
    51  		default:
    52  			if s.Scan() {
    53  				record := runtime.LogRecord{
    54  					Message: s.Text(),
    55  				}
    56  				stream.stream <- record
    57  			} else {
    58  				// TODO: is there a blocking call
    59  				// rather than a sleep loop?
    60  				time.Sleep(time.Second)
    61  			}
    62  		}
    63  	}
    64  }
    65  
    66  func (k *klog) getMatchingPods() ([]string, error) {
    67  	r := &client.Resource{
    68  		Kind:  "pod",
    69  		Value: new(client.PodList),
    70  	}
    71  
    72  	l := make(map[string]string)
    73  
    74  	l["name"] = client.Format(k.serviceName)
    75  	// TODO: specify micro:service
    76  	// l["micro"] = "service"
    77  
    78  	opts := []client.GetOption{
    79  		client.GetLabels(l),
    80  		client.GetNamespace(k.options.Namespace),
    81  	}
    82  
    83  	if err := k.client.Get(r, opts...); err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	var matches []string
    88  
    89  	for _, p := range r.Value.(*client.PodList).Items {
    90  		// find labels that match the name
    91  		if p.Metadata.Labels["name"] == client.Format(k.serviceName) {
    92  			matches = append(matches, p.Metadata.Name)
    93  		}
    94  	}
    95  
    96  	return matches, nil
    97  }
    98  
    99  func (k *klog) Read() ([]runtime.LogRecord, error) {
   100  	pods, err := k.getMatchingPods()
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	var records []runtime.LogRecord
   106  
   107  	for _, pod := range pods {
   108  		logParams := make(map[string]string)
   109  
   110  		//if !opts.Since.Equal(time.Time{}) {
   111  		//	logParams["sinceSeconds"] = strconv.Itoa(int(time.Since(opts.Since).Seconds()))
   112  		//}
   113  
   114  		if k.options.Count != 0 {
   115  			logParams["tailLines"] = strconv.Itoa(int(k.options.Count))
   116  		}
   117  
   118  		if k.options.Stream == true {
   119  			logParams["follow"] = "true"
   120  		}
   121  
   122  		opts := []client.LogOption{
   123  			client.LogParams(logParams),
   124  			client.LogNamespace(k.options.Namespace),
   125  		}
   126  
   127  		logs, err := k.client.Log(&client.Resource{
   128  			Name: pod,
   129  			Kind: "pod",
   130  		}, opts...)
   131  
   132  		if err != nil {
   133  			return nil, err
   134  		}
   135  		defer logs.Close()
   136  
   137  		s := bufio.NewScanner(logs)
   138  
   139  		for s.Scan() {
   140  			record := runtime.LogRecord{
   141  				Message: s.Text(),
   142  			}
   143  			// record.Metadata["pod"] = pod
   144  			records = append(records, record)
   145  		}
   146  	}
   147  
   148  	// sort the records
   149  	// sort.Slice(records, func(i, j int) bool { return records[i].Timestamp.Before(records[j].Timestamp) })
   150  
   151  	return records, nil
   152  }
   153  
   154  func (k *klog) Stream() (runtime.LogStream, error) {
   155  	// find the matching pods
   156  	pods, err := k.getMatchingPods()
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	stream := &kubeStream{
   162  		stream: make(chan runtime.LogRecord),
   163  		stop:   make(chan bool),
   164  	}
   165  
   166  	// stream from the individual pods
   167  	for _, pod := range pods {
   168  		go func(podName string) {
   169  			err := k.podLogStream(podName, stream)
   170  			if err != nil {
   171  				log.Errorf("Error streaming from pod: %v", err)
   172  			}
   173  		}(pod)
   174  	}
   175  
   176  	return stream, nil
   177  }
   178  
   179  // NewLog returns a configured Kubernetes logger
   180  func newLog(c client.Client, serviceName string, opts ...runtime.LogsOption) *klog {
   181  	options := runtime.LogsOptions{
   182  		Namespace: client.DefaultNamespace,
   183  	}
   184  	for _, o := range opts {
   185  		o(&options)
   186  	}
   187  
   188  	klog := &klog{
   189  		serviceName: serviceName,
   190  		client:      c,
   191  		options:     options,
   192  	}
   193  
   194  	return klog
   195  }