Skip to content

Commit

Permalink
Add --only-log-lines flag that prints only log lines (#216)
Browse files Browse the repository at this point in the history
  • Loading branch information
superbrothers committed Jan 28, 2023
1 parent 6c6db1d commit 995be39
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 29 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Supported Kubernetes resources are `pod`, `replicationcontroller`, `service`, `d
`--kubeconfig` | | Path to kubeconfig file to use. Default to KUBECONFIG variable then ~/.kube/config path.
`--namespace`, `-n` | | Kubernetes namespace to use. Default to namespace configured in kubernetes context. To specify multiple namespaces, repeat this or set comma-separated value.
`--no-follow` | `false` | Exit when all logs have been shown.
`--only-log-lines` | `false` | Print only log lines
`--output`, `-o` | `default` | Specify predefined template. Currently support: [default, raw, json, extjson, ppextjson]
`--prompt`, `-p` | `false` | Toggle interactive prompt for selecting 'app.kubernetes.io/instance' label values.
`--selector`, `-l` | | Selector (label query) to filter on. If present, default to ".*" for the pod-query.
Expand Down Expand Up @@ -237,6 +238,12 @@ Trigger the interactive prompt to select an 'app.kubernetes.io/instance' label v
stern -p
```

Output log lines only:

```
stern . --only-log-lines
```

## Completion

Stern supports command-line auto completion for bash, zsh or fish. `stern
Expand Down
3 changes: 3 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type options struct {
noFollow bool
resource string
verbosity int
onlyLogLines bool
}

func NewOptions(streams genericclioptions.IOStreams) *options {
Expand Down Expand Up @@ -331,6 +332,7 @@ func (o *options) sternConfig() (*stern.Config, error) {
Template: template,
Follow: !o.noFollow,
Resource: o.resource,
OnlyLogLines: o.onlyLogLines,

Out: o.Out,
ErrOut: o.ErrOut,
Expand Down Expand Up @@ -378,6 +380,7 @@ func (o *options) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&o.template, "template", o.template, "Template to use for log lines, leave empty to use --output flag.")
fs.BoolVarP(&o.timestamps, "timestamps", "t", o.timestamps, "Print timestamps.")
fs.StringVar(&o.timezone, "timezone", o.timezone, "Set timestamps to specific timezone.")
fs.BoolVar(&o.onlyLogLines, "only-log-lines", o.onlyLogLines, "Print only log lines")
fs.IntVar(&o.verbosity, "verbosity", o.verbosity, "Number of the log level verbosity")
fs.BoolVarP(&o.version, "version", "v", o.version, "Print the version and exit.")
}
Expand Down
1 change: 1 addition & 0 deletions stern/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type Config struct {
Template *template.Template
Follow bool
Resource string
OnlyLogLines bool

Out io.Writer
ErrOut io.Writer
Expand Down
1 change: 1 addition & 0 deletions stern/stern.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func Run(ctx context.Context, config *Config) error {
Namespace: config.AllNamespaces || len(namespaces) > 1,
TailLines: config.TailLines,
Follow: config.Follow,
OnlyLogLines: config.OnlyLogLines,
})
}

Expand Down
74 changes: 45 additions & 29 deletions stern/tail.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type TailOptions struct {
Namespace bool
TailLines *int64
Follow bool
OnlyLogLines bool
}

func (o TailOptions) IsExclude(msg string) bool {
Expand Down Expand Up @@ -107,18 +108,23 @@ func (o TailOptions) UpdateTimezoneIfNeeded(message string) (string, error) {

// NewTail returns a new tail for a Kubernetes container inside a pod
func NewTail(clientset corev1client.CoreV1Interface, nodeName, namespace, podName, containerName string, tmpl *template.Template, out, errOut io.Writer, options *TailOptions) *Tail {
podColor, containerColor := determineColor(podName)

return &Tail{
clientset: clientset,
NodeName: nodeName,
Namespace: namespace,
PodName: podName,
ContainerName: containerName,
Options: options,
closed: make(chan struct{}),
tmpl: tmpl,
active: true,
out: out,
errOut: errOut,
clientset: clientset,
NodeName: nodeName,
Namespace: namespace,
PodName: podName,
ContainerName: containerName,
Options: options,
closed: make(chan struct{}),
tmpl: tmpl,
active: true,
podColor: podColor,
containerColor: containerColor,

out: out,
errOut: errOut,
}
}

Expand All @@ -142,22 +148,13 @@ func determineColor(podName string) (podColor, containerColor *color.Color) {

// Start starts tailing
func (t *Tail) Start(ctx context.Context) error {
t.podColor, t.containerColor = determineColor(t.PodName)

ctx, cancel := context.WithCancel(ctx)
go func() {
<-t.closed
cancel()
}()

g := color.New(color.FgHiGreen, color.Bold).SprintFunc()
p := t.podColor.SprintFunc()
c := t.containerColor.SprintFunc()
if t.Options.Namespace {
fmt.Fprintf(t.errOut, "%s %s %s › %s\n", g("+"), p(t.Namespace), p(t.PodName), c(t.ContainerName))
} else {
fmt.Fprintf(t.errOut, "%s %s › %s\n", g("+"), p(t.PodName), c(t.ContainerName))
}
t.printStarting()

req := t.clientset.Pods(t.Namespace).GetLogs(t.PodName, &corev1.PodLogOptions{
Follow: t.Options.Follow,
Expand All @@ -179,18 +176,37 @@ func (t *Tail) Start(ctx context.Context) error {

// Close stops tailing
func (t *Tail) Close() {
r := color.New(color.FgHiRed, color.Bold).SprintFunc()
p := t.podColor.SprintFunc()
c := t.containerColor.SprintFunc()
if t.Options.Namespace {
fmt.Fprintf(t.errOut, "%s %s %s › %s\n", r("-"), p(t.Namespace), p(t.PodName), c(t.ContainerName))
} else {
fmt.Fprintf(t.errOut, "%s %s › %s\n", r("-"), p(t.PodName), c(t.ContainerName))
}
t.printStopping()

close(t.closed)
}

func (t *Tail) printStarting() {
if !t.Options.OnlyLogLines {
g := color.New(color.FgHiGreen, color.Bold).SprintFunc()
p := t.podColor.SprintFunc()
c := t.containerColor.SprintFunc()
if t.Options.Namespace {
fmt.Fprintf(t.errOut, "%s %s %s › %s\n", g("+"), p(t.Namespace), p(t.PodName), c(t.ContainerName))
} else {
fmt.Fprintf(t.errOut, "%s %s › %s\n", g("+"), p(t.PodName), c(t.ContainerName))
}
}
}

func (t *Tail) printStopping() {
if !t.Options.OnlyLogLines {
r := color.New(color.FgHiRed, color.Bold).SprintFunc()
p := t.podColor.SprintFunc()
c := t.containerColor.SprintFunc()
if t.Options.Namespace {
fmt.Fprintf(t.errOut, "%s %s %s › %s\n", r("-"), p(t.Namespace), p(t.PodName), c(t.ContainerName))
} else {
fmt.Fprintf(t.errOut, "%s %s › %s\n", r("-"), p(t.PodName), c(t.ContainerName))
}
}
}

// ConsumeRequest reads the data from request and writes into the out
// writer.
func (t *Tail) ConsumeRequest(ctx context.Context, request rest.ResponseWrapper) error {
Expand Down
84 changes: 84 additions & 0 deletions stern/tail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,87 @@ func (r *responseWrapperMock) DoRaw(context.Context) ([]byte, error) {
func (r *responseWrapperMock) Stream(context.Context) (io.ReadCloser, error) {
return io.NopCloser(r.data), nil
}

func TestPrintStarting(t *testing.T) {
tests := []struct {
options *TailOptions
expected []byte
}{
{
&TailOptions{},
[]byte("+ my-pod › my-container\n"),
},
{
&TailOptions{
Namespace: true,
},
[]byte("+ my-namespace my-pod › my-container\n"),
},
{
&TailOptions{
OnlyLogLines: true,
},
[]byte{},
},
{
&TailOptions{
Namespace: true,
OnlyLogLines: true,
},
[]byte{},
},
}

clientset := fake.NewSimpleClientset()
for i, tt := range tests {
errOut := new(bytes.Buffer)
tail := NewTail(clientset.CoreV1(), "my-node", "my-namespace", "my-pod", "my-container", nil, io.Discard, errOut, tt.options)
tail.printStarting()

if !bytes.Equal(tt.expected, errOut.Bytes()) {
t.Errorf("%d: expected %q, but actual %q", i, tt.expected, errOut)
}
}
}

func TestPrintStopping(t *testing.T) {
tests := []struct {
options *TailOptions
expected []byte
}{
{
&TailOptions{},
[]byte("- my-pod › my-container\n"),
},
{
&TailOptions{
Namespace: true,
},
[]byte("- my-namespace my-pod › my-container\n"),
},
{
&TailOptions{
OnlyLogLines: true,
},
[]byte{},
},
{
&TailOptions{
Namespace: true,
OnlyLogLines: true,
},
[]byte{},
},
}

clientset := fake.NewSimpleClientset()
for i, tt := range tests {
errOut := new(bytes.Buffer)
tail := NewTail(clientset.CoreV1(), "my-node", "my-namespace", "my-pod", "my-container", nil, io.Discard, errOut, tt.options)
tail.printStopping()

if !bytes.Equal(tt.expected, errOut.Bytes()) {
t.Errorf("%d: expected %q, but actual %q", i, tt.expected, errOut)
}
}
}

0 comments on commit 995be39

Please sign in to comment.