github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/debug/log/kubernetes/kubernetes.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/debug/log/kubernetes/kubernetes.go
    14  
    15  // Package kubernetes is a logger implementing (github.com/micro/go-micro/v3/debug/log).Log
    16  package kubernetes
    17  
    18  import (
    19  	"bufio"
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  	"sort"
    24  	"strconv"
    25  	"time"
    26  
    27  	"github.com/tickoalcantara12/micro/v3/service/debug/log"
    28  	"github.com/tickoalcantara12/micro/v3/service/runtime/kubernetes/client"
    29  )
    30  
    31  type klog struct {
    32  	client client.Client
    33  
    34  	log.Options
    35  }
    36  
    37  func (k *klog) podLogStream(podName string, stream *kubeStream) {
    38  	p := make(map[string]string)
    39  	p["follow"] = "true"
    40  
    41  	// get the logs for the pod
    42  	body, err := k.client.Log(&client.Resource{
    43  		Name: podName,
    44  		Kind: "pod",
    45  	}, client.LogParams(p))
    46  
    47  	if err != nil {
    48  		fmt.Fprintf(os.Stderr, err.Error())
    49  		return
    50  	}
    51  
    52  	s := bufio.NewScanner(body)
    53  	defer body.Close()
    54  
    55  	for {
    56  		select {
    57  		case <-stream.stop:
    58  			return
    59  		default:
    60  			if s.Scan() {
    61  				record := k.parse(s.Text())
    62  				stream.stream <- record
    63  			} else {
    64  				// TODO: is there a blocking call
    65  				// rather than a sleep loop?
    66  				time.Sleep(time.Second)
    67  			}
    68  		}
    69  	}
    70  }
    71  
    72  func (k *klog) getMatchingPods() ([]string, error) {
    73  	r := &client.Resource{
    74  		Kind:  "pod",
    75  		Value: new(client.PodList),
    76  	}
    77  
    78  	l := make(map[string]string)
    79  
    80  	l["name"] = client.Format(k.Options.Name)
    81  	// TODO: specify micro:service
    82  	// l["micro"] = "service"
    83  
    84  	if err := k.client.Get(r, client.GetLabels(l)); err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	var matches []string
    89  
    90  	for _, p := range r.Value.(*client.PodList).Items {
    91  		// find labels that match the name
    92  		if p.Metadata.Labels["name"] == client.Format(k.Options.Name) {
    93  			matches = append(matches, p.Metadata.Name)
    94  		}
    95  	}
    96  
    97  	return matches, nil
    98  }
    99  
   100  func (k *klog) parse(line string) log.Record {
   101  	record := log.Record{}
   102  
   103  	if err := json.Unmarshal([]byte(line), &record); err != nil {
   104  		record.Timestamp = time.Now().UTC()
   105  		record.Message = line
   106  		record.Metadata = make(map[string]string)
   107  	}
   108  
   109  	record.Metadata["service"] = k.Options.Name
   110  
   111  	return record
   112  }
   113  
   114  func (k *klog) Read(options ...log.ReadOption) ([]log.Record, error) {
   115  	opts := &log.ReadOptions{}
   116  	for _, o := range options {
   117  		o(opts)
   118  	}
   119  	pods, err := k.getMatchingPods()
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	var records []log.Record
   125  
   126  	for _, pod := range pods {
   127  		logParams := make(map[string]string)
   128  
   129  		if !opts.Since.Equal(time.Time{}) {
   130  			logParams["sinceSeconds"] = strconv.Itoa(int(time.Since(opts.Since).Seconds()))
   131  		}
   132  
   133  		if opts.Count != 0 {
   134  			logParams["tailLines"] = strconv.Itoa(opts.Count)
   135  		}
   136  
   137  		if opts.Stream == true {
   138  			logParams["follow"] = "true"
   139  		}
   140  
   141  		logs, err := k.client.Log(&client.Resource{
   142  			Name: pod,
   143  			Kind: "pod",
   144  		}, client.LogParams(logParams))
   145  
   146  		if err != nil {
   147  			return nil, err
   148  		}
   149  		defer logs.Close()
   150  
   151  		s := bufio.NewScanner(logs)
   152  
   153  		for s.Scan() {
   154  			record := k.parse(s.Text())
   155  			record.Metadata["pod"] = pod
   156  			records = append(records, record)
   157  		}
   158  	}
   159  
   160  	// sort the records
   161  	sort.Slice(records, func(i, j int) bool { return records[i].Timestamp.Before(records[j].Timestamp) })
   162  
   163  	return records, nil
   164  }
   165  
   166  func (k *klog) Write(l log.Record) error {
   167  	return write(l)
   168  }
   169  
   170  func (k *klog) Stream() (log.Stream, error) {
   171  	// find the matching pods
   172  	pods, err := k.getMatchingPods()
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	stream := &kubeStream{
   178  		stream: make(chan log.Record),
   179  		stop:   make(chan bool),
   180  	}
   181  
   182  	// stream from the individual pods
   183  	for _, pod := range pods {
   184  		go k.podLogStream(pod, stream)
   185  	}
   186  
   187  	return stream, nil
   188  }
   189  
   190  // NewLog returns a configured Kubernetes logger
   191  func NewLog(opts ...log.Option) log.Log {
   192  	klog := &klog{}
   193  	for _, o := range opts {
   194  		o(&klog.Options)
   195  	}
   196  
   197  	if len(os.Getenv("KUBERNETES_SERVICE_HOST")) > 0 {
   198  		klog.client = client.NewClusterClient()
   199  	} else {
   200  		klog.client = client.NewLocalClient()
   201  	}
   202  	return klog
   203  }