Skip to content

Commit 72a5854

Browse files
authored
add support to parse JSON logs (#228)
* add --input-format option that allows specifying message input format, and add zap-json input format support * update readme * add colorXXX functions and remove TimestampColor and CallerColor template constants, update template accordingly * change --input-format to --formatter
1 parent ef753f1 commit 72a5854

File tree

2 files changed

+48
-3
lines changed

2 files changed

+48
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Supported Kubernetes resources are `pod`, `replicationcontroller`, `service`, `d
8282
`--exclude-container`, `-E` | `[]` | Container name to exclude when multiple containers in pod. (regular expression)
8383
`--exclude-pod` | `[]` | Pod name to exclude. (regular expression)
8484
`--field-selector` | | Selector (field query) to filter on. If present, default to ".*" for the pod-query.
85+
`--formatter`, `-F` | `default` | Specify formatter template (transformation from a specific log format to human-friendly output). Currently support: [default, zap-json]
8586
`--include`, `-i` | `[]` | Log lines to include. (regular expression)
8687
`--init-containers` | `true` | Include or exclude init containers.
8788
`--kubeconfig` | | Path to kubeconfig file to use. Default to KUBECONFIG variable then ~/.kube/config path.

cmd/cmd.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type options struct {
6363
completion string
6464
template string
6565
output string
66+
formatter string
6667
prompt bool
6768
podQuery string
6869
noFollow bool
@@ -82,6 +83,7 @@ func NewOptions(streams genericclioptions.IOStreams) *options {
8283
initContainers: true,
8384
ephemeralContainers: true,
8485
output: "default",
86+
formatter: "default",
8587
since: 48 * time.Hour,
8688
tail: -1,
8789
template: "",
@@ -234,24 +236,36 @@ func (o *options) sternConfig() (*stern.Config, error) {
234236
return nil, errors.New("color should be one of 'always', 'never', or 'auto'")
235237
}
236238

239+
messageTpl := "{{.Message}}"
240+
switch o.formatter {
241+
case "zap-json":
242+
if color.NoColor {
243+
messageTpl = `{{ with $msg := .Message | parseJSON }}[{{ $msg.ts }}] {{ $msg.level }} ({{ $msg.caller }}) {{ $msg.msg }}{{ end }}`
244+
} else {
245+
messageTpl = `{{ with $msg := .Message | parseJSON }}[{{ colorGreen $msg.ts }}] {{ levelColor $msg.level }} ({{ colorCyan $msg.caller }}) {{ $msg.msg }}{{ end }}`
246+
}
247+
case "default":
248+
default:
249+
return nil, errors.New("input format should be one of 'default', 'zap-json'")
250+
}
237251
t := o.template
238252
if t == "" {
239253
switch o.output {
240254
case "default":
241255
if color.NoColor {
242-
t = "{{.PodName}} {{.ContainerName}} {{.Message}}"
256+
t = "{{.PodName}} {{.ContainerName}} " + messageTpl
243257
if o.allNamespaces || len(o.namespaces) > 1 {
244258
t = fmt.Sprintf("{{.Namespace}} %s", t)
245259
}
246260
} else {
247-
t = "{{color .PodColor .PodName}} {{color .ContainerColor .ContainerName}} {{.Message}}"
261+
t = "{{color .PodColor .PodName}} {{color .ContainerColor .ContainerName}} " + messageTpl
248262
if o.allNamespaces || len(o.namespaces) > 1 {
249263
t = fmt.Sprintf("{{color .PodColor .Namespace}} %s", t)
250264
}
251265

252266
}
253267
case "raw":
254-
t = "{{.Message}}"
268+
t = messageTpl
255269
case "json":
256270
t = "{{json .}}"
257271
case "extjson":
@@ -298,6 +312,35 @@ func (o *options) sternConfig() (*stern.Config, error) {
298312
"color": func(color color.Color, text string) string {
299313
return color.SprintFunc()(text)
300314
},
315+
"colorBlack": color.BlackString,
316+
"colorRed": color.RedString,
317+
"colorGreen": color.GreenString,
318+
"colorYellow": color.YellowString,
319+
"colorBlue": color.BlueString,
320+
"colorMagenta": color.MagentaString,
321+
"colorCyan": color.CyanString,
322+
"colorWhite": color.WhiteString,
323+
"levelColor": func(level string) string {
324+
var levelColor *color.Color
325+
switch strings.ToLower(level) {
326+
case "debug":
327+
levelColor = color.New(color.FgMagenta)
328+
case "info":
329+
levelColor = color.New(color.FgBlue)
330+
case "warn":
331+
levelColor = color.New(color.FgYellow)
332+
case "error":
333+
levelColor = color.New(color.FgRed)
334+
case "dpanic":
335+
levelColor = color.New(color.FgRed)
336+
case "panic":
337+
levelColor = color.New(color.FgRed)
338+
case "fatal":
339+
levelColor = color.New(color.FgCyan)
340+
default:
341+
}
342+
return levelColor.SprintFunc()(level)
343+
},
301344
}
302345
template, err := template.New("log").Funcs(funs).Parse(t)
303346
if err != nil {
@@ -389,6 +432,7 @@ func (o *options) AddFlags(fs *pflag.FlagSet) {
389432
fs.StringSliceVarP(&o.namespaces, "namespace", "n", o.namespaces, "Kubernetes namespace to use. Default to namespace configured in kubernetes context. To specify multiple namespaces, repeat this or set comma-separated value.")
390433
fs.IntVar(&o.maxLogRequests, "max-log-requests", o.maxLogRequests, "Maximum number of concurrent logs to request. Defaults to 50, but 5 when specifying --no-follow")
391434
fs.StringVarP(&o.output, "output", "o", o.output, "Specify predefined template. Currently support: [default, raw, json, extjson, ppextjson]")
435+
fs.StringVarP(&o.formatter, "formatter", "F", o.formatter, "Specify formatter template (transformation from a specific log format to human-friendly output). Currently support: [default, zap-json]")
392436
fs.BoolVarP(&o.prompt, "prompt", "p", o.prompt, "Toggle interactive prompt for selecting 'app.kubernetes.io/instance' label values.")
393437
fs.StringVarP(&o.selector, "selector", "l", o.selector, "Selector (label query) to filter on. If present, default to \".*\" for the pod-query.")
394438
fs.StringVar(&o.fieldSelector, "field-selector", o.fieldSelector, "Selector (field query) to filter on. If present, default to \".*\" for the pod-query.")

0 commit comments

Comments
 (0)