github.com/annwntech/go-micro/v2@v2.9.5/debug/log/kubernetes/kubernetes.go (about)

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