mirror of https://github.com/linkerd/linkerd2.git
				
				
				
			
		
			
				
	
	
		
			276 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			276 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
package cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"os/signal"
 | 
						|
	"regexp"
 | 
						|
	"text/template"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/fatih/color"
 | 
						|
	"github.com/linkerd/linkerd2/pkg/k8s"
 | 
						|
	"github.com/spf13/cobra"
 | 
						|
	"github.com/wercker/stern/stern"
 | 
						|
	corev1 "k8s.io/api/core/v1"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/labels"
 | 
						|
	"k8s.io/client-go/kubernetes"
 | 
						|
)
 | 
						|
 | 
						|
//This code replicates most of the functionality in https://github.com/wercker/stern/blob/master/cmd/cli.go
 | 
						|
type logCmdConfig struct {
 | 
						|
	clientset kubernetes.Interface
 | 
						|
	*stern.Config
 | 
						|
}
 | 
						|
 | 
						|
type logsOptions struct {
 | 
						|
	container             string
 | 
						|
	controlPlaneComponent string
 | 
						|
	noColor               bool
 | 
						|
	sinceSeconds          time.Duration
 | 
						|
	tail                  int64
 | 
						|
	timestamps            bool
 | 
						|
}
 | 
						|
 | 
						|
func newLogsOptions() *logsOptions {
 | 
						|
	return &logsOptions{
 | 
						|
		container:             "",
 | 
						|
		controlPlaneComponent: "",
 | 
						|
		noColor:               false,
 | 
						|
		sinceSeconds:          48 * time.Hour,
 | 
						|
		tail:                  -1,
 | 
						|
		timestamps:            false,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (o *logsOptions) toSternConfig(controlPlaneComponents, availableContainers []string) (*stern.Config, error) {
 | 
						|
	config := &stern.Config{}
 | 
						|
 | 
						|
	if o.controlPlaneComponent == "" {
 | 
						|
		config.LabelSelector = labels.Everything()
 | 
						|
	} else {
 | 
						|
		var podExists string
 | 
						|
		for _, p := range controlPlaneComponents {
 | 
						|
			if p == o.controlPlaneComponent {
 | 
						|
				podExists = p
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if podExists == "" {
 | 
						|
			return nil, fmt.Errorf("control plane component [%s] does not exist. Must be one of %v", o.controlPlaneComponent, controlPlaneComponents)
 | 
						|
		}
 | 
						|
		selector, err := labels.Parse(fmt.Sprintf("linkerd.io/control-plane-component=%s", o.controlPlaneComponent))
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		config.LabelSelector = selector
 | 
						|
	}
 | 
						|
 | 
						|
	if o.container != "" {
 | 
						|
		var matchingContainer string
 | 
						|
		for _, c := range availableContainers {
 | 
						|
			if o.container == c {
 | 
						|
				matchingContainer = c
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if matchingContainer == "" {
 | 
						|
			return nil, fmt.Errorf("container [%s] does not exist in control plane [%s]", o.container, controlPlaneNamespace)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	containerFilterRgx, err := regexp.Compile(o.container)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	config.ContainerQuery = containerFilterRgx
 | 
						|
 | 
						|
	if o.tail != -1 {
 | 
						|
		config.TailLines = &o.tail
 | 
						|
	}
 | 
						|
 | 
						|
	// Do not use regex to filter pods. Instead, we provide the list of all control plane components and use
 | 
						|
	// the label selector to filter logs.
 | 
						|
	podFilterRgx, err := regexp.Compile("")
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Based on stern/cmd/cli.go
 | 
						|
	t := "{{color .PodColor .PodName}} {{color .ContainerColor .ContainerName}} {{.Message}}"
 | 
						|
	if o.noColor {
 | 
						|
		t = "{{.PodName}} {{.ContainerName}} {{.Message}}"
 | 
						|
	}
 | 
						|
	funs := map[string]interface{}{
 | 
						|
		"json": func(in interface{}) (string, error) {
 | 
						|
			b, err := json.Marshal(in)
 | 
						|
			if err != nil {
 | 
						|
				return "", err
 | 
						|
			}
 | 
						|
			return string(b), nil
 | 
						|
		},
 | 
						|
		"color": func(color color.Color, text string) string {
 | 
						|
			return color.SprintFunc()(text)
 | 
						|
		},
 | 
						|
	}
 | 
						|
	template, err := template.New("log").Funcs(funs).Parse(t)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	config.PodQuery = podFilterRgx
 | 
						|
	config.Since = o.sinceSeconds
 | 
						|
	config.Timestamps = o.timestamps
 | 
						|
	config.Namespace = controlPlaneNamespace
 | 
						|
	config.ContainerState = stern.RUNNING
 | 
						|
	config.ExcludeContainerQuery = nil
 | 
						|
	config.Template = template
 | 
						|
 | 
						|
	return config, nil
 | 
						|
}
 | 
						|
 | 
						|
func getControlPlaneComponentsAndContainers(pods *corev1.PodList) ([]string, []string) {
 | 
						|
	var controlPlaneComponents, containers []string
 | 
						|
	for _, pod := range pods.Items {
 | 
						|
		controlPlaneComponents = append(controlPlaneComponents, pod.Labels["linkerd.io/control-plane-component"])
 | 
						|
		for _, container := range pod.Spec.Containers {
 | 
						|
			containers = append(containers, container.Name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return controlPlaneComponents, containers
 | 
						|
}
 | 
						|
 | 
						|
func newLogCmdConfig(options *logsOptions, kubeconfigPath, kubeContext, impersonate string, impersonateGroup []string) (*logCmdConfig, error) {
 | 
						|
	kubeAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	podList, err := kubeAPI.CoreV1().Pods(controlPlaneNamespace).List(metav1.ListOptions{})
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	components, containers := getControlPlaneComponentsAndContainers(podList)
 | 
						|
 | 
						|
	c, err := options.toSternConfig(components, containers)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return &logCmdConfig{
 | 
						|
		kubeAPI,
 | 
						|
		c,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func newCmdLogs() *cobra.Command {
 | 
						|
	options := newLogsOptions()
 | 
						|
 | 
						|
	cmd := &cobra.Command{
 | 
						|
		Use:   "logs [flags]",
 | 
						|
		Short: "Tail logs from containers in the Linkerd control plane",
 | 
						|
		Long:  `Tail logs from containers in the Linkerd control plane.`,
 | 
						|
		Example: `  # Tail logs from all containers in the prometheus control plane component
 | 
						|
  linkerd logs --control-plane-component prometheus
 | 
						|
 | 
						|
  # Tail logs from the linkerd-proxy container in the grafana control plane component
 | 
						|
  linkerd logs --control-plane-component grafana --container linkerd-proxy
 | 
						|
 | 
						|
  # Tail logs from the linkerd-proxy container in the controller component beginning with the last two lines
 | 
						|
  linkerd logs --control-plane-component controller --container linkerd-proxy --tail 2
 | 
						|
 | 
						|
  # Tail logs from the linkerd-proxy container in the controller component showing timestamps for each line
 | 
						|
  linkerd logs --control-plane-component controller --container linkerd-proxy --timestamps
 | 
						|
`,
 | 
						|
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
						|
			color.NoColor = options.noColor
 | 
						|
 | 
						|
			opts, err := newLogCmdConfig(options, kubeconfigPath, kubeContext, impersonate, impersonateGroup)
 | 
						|
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			return runLogOutput(opts)
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	cmd.PersistentFlags().StringVarP(&options.container, "container", "c", options.container, "Tail logs from the specified container. Options are 'public-api', 'destination', 'tap', 'prometheus', 'grafana' or 'linkerd-proxy'")
 | 
						|
	cmd.PersistentFlags().StringVar(&options.controlPlaneComponent, "control-plane-component", options.controlPlaneComponent, "Tail logs from the specified control plane component. Default value (empty string) causes this command to tail logs from all resources marked with the 'linkerd.io/control-plane-component' label selector")
 | 
						|
	cmd.PersistentFlags().BoolVarP(&options.noColor, "no-color", "n", options.noColor, "Disable colorized output") // needed until at least https://github.com/wercker/stern/issues/69 is resolved
 | 
						|
	cmd.PersistentFlags().DurationVarP(&options.sinceSeconds, "since", "s", options.sinceSeconds, "Duration of how far back logs should be retrieved")
 | 
						|
	cmd.PersistentFlags().Int64Var(&options.tail, "tail", options.tail, "Last number of log lines to show for a given container. -1 does not show previous log lines")
 | 
						|
	cmd.PersistentFlags().BoolVarP(&options.timestamps, "timestamps", "t", options.timestamps, "Print timestamps for each given log line")
 | 
						|
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
func runLogOutput(opts *logCmdConfig) error {
 | 
						|
	sigCh := make(chan os.Signal, 1)
 | 
						|
	signal.Notify(sigCh, os.Interrupt, os.Kill)
 | 
						|
 | 
						|
	ctx, cancel := context.WithCancel(context.Background())
 | 
						|
	defer cancel()
 | 
						|
 | 
						|
	podInterface := opts.clientset.CoreV1().Pods(opts.Namespace)
 | 
						|
	tails := make(map[string]*stern.Tail)
 | 
						|
 | 
						|
	// This channel serializes all log output.
 | 
						|
	// It is intended to workaround https://github.com/wercker/stern/issues/96,
 | 
						|
	// and is based on
 | 
						|
	// https://github.com/oandrew/stern/commit/8723308e46b408e239ce369ced12706d01479532
 | 
						|
	logC := make(chan string, 1024)
 | 
						|
 | 
						|
	go func() {
 | 
						|
		for {
 | 
						|
			select {
 | 
						|
			case str := <-logC:
 | 
						|
				fmt.Fprintf(os.Stdout, str)
 | 
						|
			case <-ctx.Done():
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	added, _, err := stern.Watch(
 | 
						|
		ctx,
 | 
						|
		podInterface,
 | 
						|
		opts.PodQuery,
 | 
						|
		opts.ContainerQuery,
 | 
						|
		opts.ExcludeContainerQuery,
 | 
						|
		opts.ContainerState,
 | 
						|
		opts.LabelSelector,
 | 
						|
	)
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	go func() {
 | 
						|
		for a := range added {
 | 
						|
			tailOpts := &stern.TailOptions{
 | 
						|
				SinceSeconds: int64(opts.Since.Seconds()),
 | 
						|
				Timestamps:   opts.Timestamps,
 | 
						|
				TailLines:    opts.TailLines,
 | 
						|
				Exclude:      opts.Exclude,
 | 
						|
				Include:      opts.Include,
 | 
						|
				Namespace:    true,
 | 
						|
			}
 | 
						|
 | 
						|
			newTail := stern.NewTail(a.Namespace, a.Pod, a.Container, opts.Template, tailOpts)
 | 
						|
			if _, ok := tails[a.GetID()]; !ok {
 | 
						|
				tails[a.GetID()] = newTail
 | 
						|
			}
 | 
						|
			newTail.Start(ctx, podInterface, logC)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	<-sigCh
 | 
						|
	return nil
 | 
						|
}
 |