Skip to content

Commit 995be39

Browse files
Add --only-log-lines flag that prints only log lines (#216)
1 parent 6c6db1d commit 995be39

File tree

6 files changed

+141
-29
lines changed

6 files changed

+141
-29
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Supported Kubernetes resources are `pod`, `replicationcontroller`, `service`, `d
8787
`--kubeconfig` | | Path to kubeconfig file to use. Default to KUBECONFIG variable then ~/.kube/config path.
8888
`--namespace`, `-n` | | Kubernetes namespace to use. Default to namespace configured in kubernetes context. To specify multiple namespaces, repeat this or set comma-separated value.
8989
`--no-follow` | `false` | Exit when all logs have been shown.
90+
`--only-log-lines` | `false` | Print only log lines
9091
`--output`, `-o` | `default` | Specify predefined template. Currently support: [default, raw, json, extjson, ppextjson]
9192
`--prompt`, `-p` | `false` | Toggle interactive prompt for selecting 'app.kubernetes.io/instance' label values.
9293
`--selector`, `-l` | | Selector (label query) to filter on. If present, default to ".*" for the pod-query.
@@ -237,6 +238,12 @@ Trigger the interactive prompt to select an 'app.kubernetes.io/instance' label v
237238
stern -p
238239
```
239240

241+
Output log lines only:
242+
243+
```
244+
stern . --only-log-lines
245+
```
246+
240247
## Completion
241248

242249
Stern supports command-line auto completion for bash, zsh or fish. `stern

cmd/cmd.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ type options struct {
6868
noFollow bool
6969
resource string
7070
verbosity int
71+
onlyLogLines bool
7172
}
7273

7374
func NewOptions(streams genericclioptions.IOStreams) *options {
@@ -331,6 +332,7 @@ func (o *options) sternConfig() (*stern.Config, error) {
331332
Template: template,
332333
Follow: !o.noFollow,
333334
Resource: o.resource,
335+
OnlyLogLines: o.onlyLogLines,
334336

335337
Out: o.Out,
336338
ErrOut: o.ErrOut,
@@ -378,6 +380,7 @@ func (o *options) AddFlags(fs *pflag.FlagSet) {
378380
fs.StringVar(&o.template, "template", o.template, "Template to use for log lines, leave empty to use --output flag.")
379381
fs.BoolVarP(&o.timestamps, "timestamps", "t", o.timestamps, "Print timestamps.")
380382
fs.StringVar(&o.timezone, "timezone", o.timezone, "Set timestamps to specific timezone.")
383+
fs.BoolVar(&o.onlyLogLines, "only-log-lines", o.onlyLogLines, "Print only log lines")
381384
fs.IntVar(&o.verbosity, "verbosity", o.verbosity, "Number of the log level verbosity")
382385
fs.BoolVarP(&o.version, "version", "v", o.version, "Print the version and exit.")
383386
}

stern/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type Config struct {
4848
Template *template.Template
4949
Follow bool
5050
Resource string
51+
OnlyLogLines bool
5152

5253
Out io.Writer
5354
ErrOut io.Writer

stern/stern.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ func Run(ctx context.Context, config *Config) error {
121121
Namespace: config.AllNamespaces || len(namespaces) > 1,
122122
TailLines: config.TailLines,
123123
Follow: config.Follow,
124+
OnlyLogLines: config.OnlyLogLines,
124125
})
125126
}
126127

stern/tail.go

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ type TailOptions struct {
6060
Namespace bool
6161
TailLines *int64
6262
Follow bool
63+
OnlyLogLines bool
6364
}
6465

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

108109
// NewTail returns a new tail for a Kubernetes container inside a pod
109110
func NewTail(clientset corev1client.CoreV1Interface, nodeName, namespace, podName, containerName string, tmpl *template.Template, out, errOut io.Writer, options *TailOptions) *Tail {
111+
podColor, containerColor := determineColor(podName)
112+
110113
return &Tail{
111-
clientset: clientset,
112-
NodeName: nodeName,
113-
Namespace: namespace,
114-
PodName: podName,
115-
ContainerName: containerName,
116-
Options: options,
117-
closed: make(chan struct{}),
118-
tmpl: tmpl,
119-
active: true,
120-
out: out,
121-
errOut: errOut,
114+
clientset: clientset,
115+
NodeName: nodeName,
116+
Namespace: namespace,
117+
PodName: podName,
118+
ContainerName: containerName,
119+
Options: options,
120+
closed: make(chan struct{}),
121+
tmpl: tmpl,
122+
active: true,
123+
podColor: podColor,
124+
containerColor: containerColor,
125+
126+
out: out,
127+
errOut: errOut,
122128
}
123129
}
124130

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

143149
// Start starts tailing
144150
func (t *Tail) Start(ctx context.Context) error {
145-
t.podColor, t.containerColor = determineColor(t.PodName)
146-
147151
ctx, cancel := context.WithCancel(ctx)
148152
go func() {
149153
<-t.closed
150154
cancel()
151155
}()
152156

153-
g := color.New(color.FgHiGreen, color.Bold).SprintFunc()
154-
p := t.podColor.SprintFunc()
155-
c := t.containerColor.SprintFunc()
156-
if t.Options.Namespace {
157-
fmt.Fprintf(t.errOut, "%s %s %s › %s\n", g("+"), p(t.Namespace), p(t.PodName), c(t.ContainerName))
158-
} else {
159-
fmt.Fprintf(t.errOut, "%s %s › %s\n", g("+"), p(t.PodName), c(t.ContainerName))
160-
}
157+
t.printStarting()
161158

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

180177
// Close stops tailing
181178
func (t *Tail) Close() {
182-
r := color.New(color.FgHiRed, color.Bold).SprintFunc()
183-
p := t.podColor.SprintFunc()
184-
c := t.containerColor.SprintFunc()
185-
if t.Options.Namespace {
186-
fmt.Fprintf(t.errOut, "%s %s %s › %s\n", r("-"), p(t.Namespace), p(t.PodName), c(t.ContainerName))
187-
} else {
188-
fmt.Fprintf(t.errOut, "%s %s › %s\n", r("-"), p(t.PodName), c(t.ContainerName))
189-
}
179+
t.printStopping()
190180

191181
close(t.closed)
192182
}
193183

184+
func (t *Tail) printStarting() {
185+
if !t.Options.OnlyLogLines {
186+
g := color.New(color.FgHiGreen, color.Bold).SprintFunc()
187+
p := t.podColor.SprintFunc()
188+
c := t.containerColor.SprintFunc()
189+
if t.Options.Namespace {
190+
fmt.Fprintf(t.errOut, "%s %s %s › %s\n", g("+"), p(t.Namespace), p(t.PodName), c(t.ContainerName))
191+
} else {
192+
fmt.Fprintf(t.errOut, "%s %s › %s\n", g("+"), p(t.PodName), c(t.ContainerName))
193+
}
194+
}
195+
}
196+
197+
func (t *Tail) printStopping() {
198+
if !t.Options.OnlyLogLines {
199+
r := color.New(color.FgHiRed, color.Bold).SprintFunc()
200+
p := t.podColor.SprintFunc()
201+
c := t.containerColor.SprintFunc()
202+
if t.Options.Namespace {
203+
fmt.Fprintf(t.errOut, "%s %s %s › %s\n", r("-"), p(t.Namespace), p(t.PodName), c(t.ContainerName))
204+
} else {
205+
fmt.Fprintf(t.errOut, "%s %s › %s\n", r("-"), p(t.PodName), c(t.ContainerName))
206+
}
207+
}
208+
}
209+
194210
// ConsumeRequest reads the data from request and writes into the out
195211
// writer.
196212
func (t *Tail) ConsumeRequest(ctx context.Context, request rest.ResponseWrapper) error {

stern/tail_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,87 @@ func (r *responseWrapperMock) DoRaw(context.Context) ([]byte, error) {
197197
func (r *responseWrapperMock) Stream(context.Context) (io.ReadCloser, error) {
198198
return io.NopCloser(r.data), nil
199199
}
200+
201+
func TestPrintStarting(t *testing.T) {
202+
tests := []struct {
203+
options *TailOptions
204+
expected []byte
205+
}{
206+
{
207+
&TailOptions{},
208+
[]byte("+ my-pod › my-container\n"),
209+
},
210+
{
211+
&TailOptions{
212+
Namespace: true,
213+
},
214+
[]byte("+ my-namespace my-pod › my-container\n"),
215+
},
216+
{
217+
&TailOptions{
218+
OnlyLogLines: true,
219+
},
220+
[]byte{},
221+
},
222+
{
223+
&TailOptions{
224+
Namespace: true,
225+
OnlyLogLines: true,
226+
},
227+
[]byte{},
228+
},
229+
}
230+
231+
clientset := fake.NewSimpleClientset()
232+
for i, tt := range tests {
233+
errOut := new(bytes.Buffer)
234+
tail := NewTail(clientset.CoreV1(), "my-node", "my-namespace", "my-pod", "my-container", nil, io.Discard, errOut, tt.options)
235+
tail.printStarting()
236+
237+
if !bytes.Equal(tt.expected, errOut.Bytes()) {
238+
t.Errorf("%d: expected %q, but actual %q", i, tt.expected, errOut)
239+
}
240+
}
241+
}
242+
243+
func TestPrintStopping(t *testing.T) {
244+
tests := []struct {
245+
options *TailOptions
246+
expected []byte
247+
}{
248+
{
249+
&TailOptions{},
250+
[]byte("- my-pod › my-container\n"),
251+
},
252+
{
253+
&TailOptions{
254+
Namespace: true,
255+
},
256+
[]byte("- my-namespace my-pod › my-container\n"),
257+
},
258+
{
259+
&TailOptions{
260+
OnlyLogLines: true,
261+
},
262+
[]byte{},
263+
},
264+
{
265+
&TailOptions{
266+
Namespace: true,
267+
OnlyLogLines: true,
268+
},
269+
[]byte{},
270+
},
271+
}
272+
273+
clientset := fake.NewSimpleClientset()
274+
for i, tt := range tests {
275+
errOut := new(bytes.Buffer)
276+
tail := NewTail(clientset.CoreV1(), "my-node", "my-namespace", "my-pod", "my-container", nil, io.Discard, errOut, tt.options)
277+
tail.printStopping()
278+
279+
if !bytes.Equal(tt.expected, errOut.Bytes()) {
280+
t.Errorf("%d: expected %q, but actual %q", i, tt.expected, errOut)
281+
}
282+
}
283+
}

0 commit comments

Comments
 (0)