diff --git a/cmd/loki/main.go b/cmd/loki/main.go index c287b3f6b202..1753aca06d47 100644 --- a/cmd/loki/main.go +++ b/cmd/loki/main.go @@ -1,10 +1,13 @@ package main import ( + "errors" "flag" "fmt" + "io" "os" "reflect" + "strings" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" @@ -26,9 +29,13 @@ func init() { prometheus.MustRegister(version.NewCollector("loki")) } +var lineReplacer = strings.NewReplacer("\n", "\\n ") + func main() { printVersion := flag.Bool("version", false, "Print this builds version information") - printConfig := flag.Bool("print-config-stderr", false, "Dump the entire YAML config Loki will run with to stderr") + printConfig := flag.Bool("print-config-stderr", false, "Dump the entire Loki config object to stderr") + printConfigInline := flag.Bool("print-config-stderr-inline", false, "Dump the entire Loki config object to stderr broken up by sections with escaped newline characters, "+ + "this will display much better when captured by Loki and shown in Grafana") var config loki.Config if err := cfg.Parse(&config); err != nil { @@ -61,11 +68,16 @@ func main() { } if *printConfig { - lc, err := yaml.Marshal(&config) + err := dumpConfig(os.Stderr, &config, false) if err != nil { - level.Error(util.Logger).Log("msg", "failed to marshal config for pretty printing", "err", err.Error()) - } else { - fmt.Fprintf(os.Stderr, "---\n# Loki Config\n# %s\n%s\n\n", version.Info(), string(lc)) + level.Error(util.Logger).Log("msg", "failed to print config to stderr", "err", err.Error()) + } + } + + if *printConfigInline { + err := dumpConfig(os.Stderr, &config, true) + if err != nil { + level.Error(util.Logger).Log("msg", "failed to print config to stderr", "err", err.Error()) } } @@ -94,3 +106,76 @@ func main() { err = t.Run() util.CheckFatal("running loki", err) } + +func dumpConfig(w io.Writer, config *loki.Config, escapeNewlines bool) error { + if !escapeNewlines { + lc, err := yaml.Marshal(&config) + if err != nil { + return err + } + fmt.Fprintf(w, "---\n# Loki Config\n# %s\n%s\n\n", version.Info(), string(lc)) + return nil + } + + s := reflect.ValueOf(config).Elem() + typeOfT := s.Type() + + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + if !f.CanInterface() { + continue + } + // Lookup the yaml tag to print the field name by the yaml tag, skip if it's not present or empty + if alias, ok := typeOfT.Field(i).Tag.Lookup("yaml"); ok { + if alias == "" { + continue + } else { + // yaml tags can contain other data like `omitempty` so split by comma and only return first result + fmt.Fprint(w, strings.Split(alias, ",")[0]+": ") + } + } else { + continue + } + + if f.Kind() == reflect.Slice { + return errors.New("the config dumping function was not programmed to handle a configuration array as a top level item in the Loki config") + } + + kind := f.Kind() + val := f.Interface() + + // Dereference any pointers so we can properly determine the kind of the underlying object + if f.Kind() == reflect.Ptr { + if !f.IsNil() { + kind = f.Elem().Kind() + val = f.Elem().Interface() + } + } + + // If the field is a struct, unmarshal it with a yaml unmarshaller and print it + if kind == reflect.Struct { + err := unmarshall(w, val) + if err != nil { + return err + } + } else { + // If it's not a struct, just print the value + fmt.Fprint(w, val) + } + fmt.Fprint(w, "\n") + } + + os.Exit(0) + + return nil +} + +func unmarshall(w io.Writer, v interface{}) error { + fmt.Fprint(w, "\\n") + sc, err := yaml.Marshal(v) + if err != nil { + return err + } + fmt.Fprint(w, lineReplacer.Replace(string(sc))) + return nil +}