diff --git a/cmd/nfd-worker/main.go b/cmd/nfd-worker/main.go index 3182a6a53..b3575e242 100644 --- a/cmd/nfd-worker/main.go +++ b/cmd/nfd-worker/main.go @@ -19,8 +19,10 @@ package main import ( "flag" "fmt" - "log" "os" + "strings" + + "k8s.io/klog/v2" worker "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker" "sigs.k8s.io/node-feature-discovery/pkg/utils" @@ -46,17 +48,17 @@ func main() { // Assert that the version is known if version.Undefined() { - log.Printf("WARNING: version not set! Set -ldflags \"-X sigs.k8s.io/node-feature-discovery/pkg/version.version=`git describe --tags --dirty --always`\" during build or run.") + klog.Warningf("version not set! Set -ldflags \"-X sigs.k8s.io/node-feature-discovery/pkg/version.version=`git describe --tags --dirty --always`\" during build or run.") } // Get new NfdWorker instance instance, err := worker.NewNfdWorker(args) if err != nil { - log.Fatalf("Failed to initialize NfdWorker instance: %v", err) + klog.Fatalf("Failed to initialize NfdWorker instance: %v", err) } if err = instance.Run(); err != nil { - log.Fatalf("ERROR: %v", err) + klog.Fatal(err) } } @@ -76,13 +78,13 @@ func parseArgs(flags *flag.FlagSet, osArgs ...string) *worker.Args { case "no-publish": args.Overrides.NoPublish = overrides.NoPublish case "label-whitelist": - log.Printf("WARNING: --label-whitelist is deprecated, use 'core.labelWhiteList' option in the config file, instead") + klog.Warningf("--label-whitelist is deprecated, use 'core.labelWhiteList' option in the config file, instead") args.Overrides.LabelWhiteList = overrides.LabelWhiteList case "sleep-interval": - log.Printf("WARNING: --sleep-interval is deprecated, use 'core.sleepInterval' option in the config file, instead") + klog.Warningf("--sleep-interval is deprecated, use 'core.sleepInterval' option in the config file, instead") args.Overrides.SleepInterval = overrides.SleepInterval case "sources": - log.Printf("WARNING: --sources is deprecated, use 'core.sources' option in the config file, instead") + klog.Warningf("--sources is deprecated, use 'core.sources' option in the config file, instead") args.Overrides.Sources = overrides.Sources } }) @@ -111,6 +113,8 @@ func initFlags(flagset *flag.FlagSet) (*worker.Args, *worker.ConfigOverrideArgs) flagset.StringVar(&args.ServerNameOverride, "server-name-override", "", "Hostname expected from server certificate, useful in testing") + initKlogFlags(flagset, args) + // Flags overlapping with config file options overrides := &worker.ConfigOverrideArgs{ LabelWhiteList: &utils.RegexpVal{}, @@ -131,3 +135,24 @@ func initFlags(flagset *flag.FlagSet) (*worker.Args, *worker.ConfigOverrideArgs) return args, overrides } + +func initKlogFlags(flagset *flag.FlagSet, args *worker.Args) { + args.Klog = make(map[string]*utils.KlogFlagVal) + + flags := flag.NewFlagSet("klog flags", flag.ContinueOnError) + //flags.SetOutput(ioutil.Discard) + klog.InitFlags(flags) + flags.VisitAll(func(f *flag.Flag) { + name := klogConfigOptName(f.Name) + args.Klog[name] = utils.NewKlogFlagVal(f) + flagset.Var(args.Klog[name], f.Name, f.Usage) + }) +} + +func klogConfigOptName(flagName string) string { + split := strings.Split(flagName, "_") + for i, v := range split[1:] { + split[i+1] = strings.Title(v) + } + return strings.Join(split, "") +} diff --git a/cmd/nfd-worker/main_test.go b/cmd/nfd-worker/main_test.go index 9481f8116..d36da7b57 100644 --- a/cmd/nfd-worker/main_test.go +++ b/cmd/nfd-worker/main_test.go @@ -59,3 +59,19 @@ func TestParseArgs(t *testing.T) { }) }) } + +func TestKlogConfigOptName(t *testing.T) { + Convey("When converting names of klog command line flags", t, func() { + tcs := map[string]string{ + "": "", + "a": "a", + "an_arg": "anArg", + "arg_with_many_parts": "argWithManyParts", + } + Convey("resulting config option names should be as expected", func() { + for input, expected := range tcs { + So(klogConfigOptName(input), ShouldEqual, expected) + } + }) + }) +} diff --git a/deployment/node-feature-discovery/values.yaml b/deployment/node-feature-discovery/values.yaml index 2675f1e9b..2660d3a7f 100644 --- a/deployment/node-feature-discovery/values.yaml +++ b/deployment/node-feature-discovery/values.yaml @@ -77,6 +77,21 @@ worker: # noPublish: false # sleepInterval: 60s # sources: [all] + # klog: + # addDirHeader: false + # alsologtostderr: false + # logBacktraceAt: + # logtostderr: true + # skipHeaders: false + # stderrthreshold: 2 + # v: 0 + # vmodule: + ## NOTE: the following options are not dynamically run-time configurable + ## and require a nfd-worker restart to take effect after being changed + # logDir: + # logFile: + # logFileMaxSize: 1800 + # skipLogHeaders: false #sources: # cpu: # cpuid: diff --git a/docs/advanced/worker-commandline-reference.md b/docs/advanced/worker-commandline-reference.md index d33013e30..3a4fa3fb2 100644 --- a/docs/advanced/worker-commandline-reference.md +++ b/docs/advanced/worker-commandline-reference.md @@ -227,3 +227,84 @@ nfd-worker -sleep-interval=1h **DEPRECATED**: you should use the `core.sleepInterval` option in the configuration file, instead. +### Logging + +The following logging-related flags are inherited from the +[klog](https://pkg.go.dev/k8s.io/klog/v2) package. + +Note: The logger setup can also be specified via the `core.klog` configuration +file options. However, the command line flags take precedence over any +corresponding config file options specified. + +#### -add_dir_header + +If true, adds the file directory to the header of the log messages. + +Default: false + +#### -alsologtostderr + +Log to standard error as well as files. + +Default: false + +#### -log_backtrace_at + +When logging hits line file:N, emit a stack trace. + +Default: *empty* + +#### -log_dir + +If non-empty, write log files in this directory. + +Default: *empty* + +#### -log_file + +If non-empty, use this log file. + +Default: *empty* + +#### -log_file_max_size + +Defines the maximum size a log file can grow to. Unit is megabytes. If the +value is 0, the maximum file size is unlimited. + +Default: 1800 + +#### -logtostderr + +Log to standard error instead of files + +Default: true + +#### -skip_headers + +If true, avoid header prefixes in the log messages. + +Default: false + +#### -skip_log_headers + +If true, avoid headers when opening log files. + +Default: false + +#### -stderrthreshold + +Logs at or above this threshold go to stderr. + +Default: 2 + +#### -v + +Number for the log level verbosity. + +Default: 0 + +#### -vmodule + +Comma-separated list of `pattern=N` settings for file-filtered logging. + +Default: *empty* diff --git a/docs/advanced/worker-configuration-reference.md b/docs/advanced/worker-configuration-reference.md index c26d8bcc3..dfebcd0c9 100644 --- a/docs/advanced/worker-configuration-reference.md +++ b/docs/advanced/worker-configuration-reference.md @@ -104,6 +104,109 @@ core: noPublish: true ``` +### core.klog + +The following options specify the logger configuration. Most of which can be +dynamically adjusted at run-time. + +Note: The logger options can also be specified via command line flags which +take precedence over any corresponding config file options. + +#### core.klog.addDirHeader + +If true, adds the file directory to the header of the log messages. + +Default: false + +Run-time configurable: yes + +#### core.klog.alsologtostderr + +Log to standard error as well as files. + +Default: false + +Run-time configurable: yes + +#### core.klog.logBacktraceAt + +When logging hits line file:N, emit a stack trace. + +Default: *empty* + +Run-time configurable: yes + +#### core.klog.logDir + +If non-empty, write log files in this directory. + +Default: *empty* + +Run-time configurable: no + +#### core.klog.logFile + +If non-empty, use this log file. + +Default: *empty* + +Run-time configurable: no + +#### core.klog.logFileMaxSize + +Defines the maximum size a log file can grow to. Unit is megabytes. If the +value is 0, the maximum file size is unlimited. + +Default: 1800 + +Run-time configurable: no + +#### core.klog.logtostderr + +Log to standard error instead of files + +Default: true + +Run-time configurable: yes + +#### core.klog.skipHeaders + +If true, avoid header prefixes in the log messages. + +Default: false + +Run-time configurable: yes + +#### core.klog.skipLogHeaders + +If true, avoid headers when opening log files. + +Default: false + +Run-time configurable: no + +#### core.klog.stderrthreshold + +Logs at or above this threshold go to stderr (default 2) + +Run-time configurable: yes + +#### core.klog.v + +Number for the log level verbosity. + +Default: 0 + +Run-time configurable: yes + +#### core.klog.vmodule + +Comma-separated list of `pattern=N` settings for file-filtered logging. + +Default: *empty* + +Run-time configurable: yes + ## sources The `sources` section contains feature source specific configuration parameters. diff --git a/nfd-daemonset-combined.yaml.template b/nfd-daemonset-combined.yaml.template index 7c14f7c70..1a912e50a 100644 --- a/nfd-daemonset-combined.yaml.template +++ b/nfd-daemonset-combined.yaml.template @@ -156,6 +156,21 @@ data: # noPublish: false # sleepInterval: 60s # sources: [all] + # klog: + # addDirHeader: false + # alsologtostderr: false + # logBacktraceAt: + # logtostderr: true + # skipHeaders: false + # stderrthreshold: 2 + # v: 0 + # vmodule: + ## NOTE: the following options are not dynamically run-time configurable + ## and require a nfd-worker restart to take effect after being changed + # logDir: + # logFile: + # logFileMaxSize: 1800 + # skipLogHeaders: false #sources: # cpu: # cpuid: diff --git a/nfd-worker-daemonset.yaml.template b/nfd-worker-daemonset.yaml.template index d9d6efbf9..603523a26 100644 --- a/nfd-worker-daemonset.yaml.template +++ b/nfd-worker-daemonset.yaml.template @@ -118,6 +118,21 @@ data: # noPublish: false # sleepInterval: 60s # sources: [all] + # klog: + # addDirHeader: false + # alsologtostderr: false + # logBacktraceAt: + # logtostderr: true + # skipHeaders: false + # stderrthreshold: 2 + # v: 0 + # vmodule: + ## NOTE: the following options are not dynamically run-time configurable + ## and require a nfd-worker restart to take effect after being changed + # logDir: + # logFile: + # logFileMaxSize: 1800 + # skipLogHeaders: false #sources: # cpu: # cpuid: diff --git a/nfd-worker-job.yaml.template b/nfd-worker-job.yaml.template index 7fd8a0b65..a9c7df96c 100644 --- a/nfd-worker-job.yaml.template +++ b/nfd-worker-job.yaml.template @@ -128,6 +128,21 @@ data: # noPublish: false # sleepInterval: 60s # sources: [all] + # klog: + # addDirHeader: false + # alsologtostderr: false + # logBacktraceAt: + # logtostderr: true + # skipHeaders: false + # stderrthreshold: 2 + # v: 0 + # vmodule: + ## NOTE: the following options are not dynamically run-time configurable + ## and require a nfd-worker restart to take effect after being changed + # logDir: + # logFile: + # logFileMaxSize: 1800 + # skipLogHeaders: false #sources: # cpu: # cpuid: diff --git a/nfd-worker.conf.example b/nfd-worker.conf.example index 1f60ae521..3585b6ec9 100644 --- a/nfd-worker.conf.example +++ b/nfd-worker.conf.example @@ -3,6 +3,21 @@ # noPublish: false # sleepInterval: 60s # sources: [all] +# klog: +# addDirHeader: false +# alsologtostderr: false +# logBacktraceAt: +# logtostderr: true +# skipHeaders: false +# stderrthreshold: 2 +# v: 0 +# vmodule: +## NOTE: the following options are not dynamically run-time configurable +## and require a nfd-worker restart to take effect after being changed +# logDir: +# logFile: +# logFileMaxSize: 1800 +# skipLogHeaders: false #sources: # cpu: # cpuid: diff --git a/pkg/nfd-worker/nfd-worker.go b/pkg/nfd-worker/nfd-worker.go index 36beb6f8d..1ad42adde 100644 --- a/pkg/nfd-worker/nfd-worker.go +++ b/pkg/nfd-worker/nfd-worker.go @@ -22,7 +22,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "os" "path/filepath" "regexp" @@ -34,6 +33,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/klog/v2" "sigs.k8s.io/yaml" pb "sigs.k8s.io/node-feature-discovery/pkg/labeler" @@ -56,9 +56,7 @@ import ( ) var ( - stdoutLogger = log.New(os.Stdout, "", log.LstdFlags) - stderrLogger = log.New(os.Stderr, "", log.LstdFlags) - nodeName = os.Getenv("NODE_NAME") + nodeName = os.Getenv("NODE_NAME") ) // Global config @@ -68,6 +66,7 @@ type NFDConfig struct { } type coreConfig struct { + Klog map[string]string LabelWhiteList utils.RegexpVal NoPublish bool Sources []string @@ -90,6 +89,7 @@ type Args struct { Server string ServerNameOverride string + Klog map[string]*utils.KlogFlagVal Overrides ConfigOverrideArgs } @@ -186,9 +186,9 @@ func addConfigWatch(path string) (*fsnotify.Watcher, map[string]struct{}, error) for p := path; ; p = filepath.Dir(p) { if err := w.Add(p); err != nil { - stdoutLogger.Printf("failed to add fsnotify watch for %q: %v", p, err) + klog.V(1).Infof("failed to add fsnotify watch for %q: %v", p, err) } else { - stdoutLogger.Printf("added fsnotify watch %q", p) + klog.V(1).Infof("added fsnotify watch %q", p) added = true } @@ -211,6 +211,7 @@ func newDefaultConfig() *NFDConfig { LabelWhiteList: utils.RegexpVal{Regexp: *regexp.MustCompile("")}, SleepInterval: duration{60 * time.Second}, Sources: []string{"all"}, + Klog: make(map[string]string), }, } } @@ -218,8 +219,8 @@ func newDefaultConfig() *NFDConfig { // Run NfdWorker client. Returns if a fatal error is encountered, or, after // one request if OneShot is set to 'true' in the worker args. func (w *nfdWorker) Run() error { - stdoutLogger.Printf("Node Feature Discovery Worker %s", version.Get()) - stdoutLogger.Printf("NodeName: '%s'", nodeName) + klog.Infof("Node Feature Discovery Worker %s", version.Get()) + klog.Infof("NodeName: '%s'", nodeName) // Create watcher for config file and read initial configuration configWatch, paths, err := addConfigWatch(w.configFilePath) @@ -266,11 +267,11 @@ func (w *nfdWorker) Run() error { // If any of our paths (directories or the file itself) change if _, ok := paths[name]; ok { - stdoutLogger.Printf("fsnotify event in %q detected, reconfiguring fsnotify and reloading configuration", name) + klog.Infof("fsnotify event in %q detected, reconfiguring fsnotify and reloading configuration", name) // Blindly remove existing watch and add a new one if err := configWatch.Close(); err != nil { - stderrLogger.Printf("WARNING: failed to close fsnotify watcher: %v", err) + klog.Warningf("failed to close fsnotify watcher: %v", err) } configWatch, paths, err = addConfigWatch(w.configFilePath) if err != nil { @@ -284,7 +285,7 @@ func (w *nfdWorker) Run() error { } case e := <-configWatch.Errors: - stderrLogger.Printf("ERROR: config file watcher error: %v", e) + klog.Errorf("config file watcher error: %v", e) case <-configTrigger: if err := w.configure(w.configFilePath, w.args.Options); err != nil { @@ -303,7 +304,7 @@ func (w *nfdWorker) Run() error { labelTrigger = time.After(0) case <-w.stop: - stdoutLogger.Printf("shutting down nfd-worker") + klog.Infof("shutting down nfd-worker") configWatch.Close() return nil } @@ -380,14 +381,32 @@ func (w *nfdWorker) disconnect() { func (c *coreConfig) sanitize() { if c.SleepInterval.Duration > 0 && c.SleepInterval.Duration < time.Second { - stderrLogger.Printf("WARNING: too short sleep-intervall specified (%s), forcing to 1s", + klog.Warningf("too short sleep-intervall specified (%s), forcing to 1s", c.SleepInterval.Duration.String()) c.SleepInterval = duration{time.Second} } } -func (w *nfdWorker) configureCore(c coreConfig) { - // Determine enabled feature sourcds +func (w *nfdWorker) configureCore(c coreConfig) error { + // Handle klog + for k, a := range w.args.Klog { + if !a.IsSetFromCmdline() { + v, ok := c.Klog[k] + if !ok { + v = a.DefValue() + } + if err := a.SetFromConfig(v); err != nil { + return err + } + } + } + for k := range c.Klog { + if _, ok := w.args.Klog[k]; !ok { + klog.Warningf("unknown logger option in config: %q", k) + } + } + + // Determine enabled feature sources sourceList := map[string]struct{}{} all := false for _, s := range c.Sources { @@ -416,8 +435,9 @@ func (w *nfdWorker) configureCore(c coreConfig) { for n := range sourceList { names = append(names, n) } - stderrLogger.Printf("WARNING: skipping unknown source(s) %q specified in core.sources (or --sources)", strings.Join(names, ", ")) + klog.Warningf("skipping unknown source(s) %q specified in core.sources (or --sources)", strings.Join(names, ", ")) } + return nil } // Parse configuration options @@ -435,7 +455,7 @@ func (w *nfdWorker) configure(filepath string, overrides string) error { data, err := ioutil.ReadFile(filepath) if err != nil { if os.IsNotExist(err) { - stderrLogger.Printf("config file %q not found, using defaults", filepath) + klog.Infof("config file %q not found, using defaults", filepath) } else { return fmt.Errorf("error reading config file: %s", err) } @@ -444,7 +464,7 @@ func (w *nfdWorker) configure(filepath string, overrides string) error { if err != nil { return fmt.Errorf("Failed to parse config file: %s", err) } - stdoutLogger.Printf("Configuration successfully loaded from %q", filepath) + klog.Infof("Configuration successfully loaded from %q", filepath) } } @@ -470,7 +490,9 @@ func (w *nfdWorker) configure(filepath string, overrides string) error { w.config = c - w.configureCore(c.Core) + if err := w.configureCore(c.Core); err != nil { + return err + } // (Re-)configure all "real" sources, test sources are not configurable for _, s := range allSources { @@ -489,14 +511,13 @@ func createFeatureLabels(sources []source.FeatureSource, labelWhiteList regexp.R for _, source := range sources { labelsFromSource, err := getFeatureLabels(source, labelWhiteList) if err != nil { - stderrLogger.Printf("discovery failed for source [%s]: %s", source.Name(), err.Error()) - stderrLogger.Printf("continuing ...") + klog.Errorf("discovery failed for source %q: %v", source.Name(), err) continue } for name, value := range labelsFromSource { // Log discovered feature. - stdoutLogger.Printf("%s = %s", name, value) + klog.Infof("%s = %s", name, value) labels[name] = value } } @@ -508,7 +529,7 @@ func createFeatureLabels(sources []source.FeatureSource, labelWhiteList regexp.R func getFeatureLabels(source source.FeatureSource, labelWhiteList regexp.Regexp) (labels Labels, err error) { defer func() { if r := recover(); r != nil { - stderrLogger.Printf("panic occurred during discovery of source [%s]: %v", source.Name(), r) + klog.Errorf("panic occurred during discovery of source [%s]: %v", source.Name(), r) err = fmt.Errorf("%v", r) } }() @@ -546,7 +567,7 @@ func getFeatureLabels(source source.FeatureSource, labelWhiteList regexp.Regexp) // Validate label name. errs := validation.IsQualifiedName(nameForValidation) if len(errs) > 0 { - stderrLogger.Printf("Ignoring invalid feature name '%s': %s", label, errs) + klog.Warningf("Ignoring invalid feature name '%s': %s", label, errs) continue } @@ -554,13 +575,13 @@ func getFeatureLabels(source source.FeatureSource, labelWhiteList regexp.Regexp) // Validate label value errs = validation.IsValidLabelValue(value) if len(errs) > 0 { - stderrLogger.Printf("Ignoring invalid feature value %s=%s: %s", label, value, errs) + klog.Warningf("Ignoring invalid feature value %s=%s: %s", label, value, errs) continue } // Skip if label doesn't match labelWhiteList if !labelWhiteList.MatchString(nameForWhiteListing) { - stderrLogger.Printf("%q does not match the whitelist (%s) and will not be published.", nameForWhiteListing, labelWhiteList.String()) + klog.Infof("%q does not match the whitelist (%s) and will not be published.", nameForWhiteListing, labelWhiteList.String()) continue } @@ -575,14 +596,14 @@ func advertiseFeatureLabels(client pb.LabelerClient, labels Labels) error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - stdoutLogger.Printf("Sending labeling request to nfd-master") + klog.Infof("Sending labeling request to nfd-master") labelReq := pb.SetLabelsRequest{Labels: labels, NfdVersion: version.Get(), NodeName: nodeName} _, err := client.SetLabels(ctx, &labelReq) if err != nil { - stderrLogger.Printf("failed to set node labels: %v", err) + klog.Errorf("failed to set node labels: %v", err) return err } diff --git a/pkg/utils/flags.go b/pkg/utils/flags.go index b91c70c7f..c88a2e59a 100644 --- a/pkg/utils/flags.go +++ b/pkg/utils/flags.go @@ -18,6 +18,7 @@ package utils import ( "encoding/json" + "flag" "fmt" "regexp" "sort" @@ -98,3 +99,64 @@ func (a *StringSliceVal) String() string { } return strings.Join(*a, ",") } + +// KlogFlagVal is a wrapper to allow dynamic control of klog from the config file +type KlogFlagVal struct { + flag *flag.Flag + isSetFromCmdLine bool +} + +// Set implements flag.Value interface +func (k *KlogFlagVal) Set(value string) error { + k.isSetFromCmdLine = true + return k.flag.Value.Set(value) +} + +// String implements flag.Value interface +func (k *KlogFlagVal) String() string { + if k.flag == nil { + return "" + } + // Need to handle "log_backtrace_at" in a special way + s := k.flag.Value.String() + if k.flag.Name == "log_backtrace_at" && s == ":0" { + s = "" + } + return s +} + +// DefValue returns the default value of KlogFlagVal as string +func (k *KlogFlagVal) DefValue() string { + // Need to handle "log_backtrace_at" in a special way + d := k.flag.DefValue + if k.flag.Name == "log_backtrace_at" && d == ":0" { + d = "" + } + return d +} + +// SetFromConfig sets the value without marking it as set from the cmdline +func (k *KlogFlagVal) SetFromConfig(value string) error { + return k.flag.Value.Set(value) +} + +// IsSetFromCmdline returns true if the value has been set via Set() +func (k *KlogFlagVal) IsSetFromCmdline() bool { return k.isSetFromCmdLine } + +// IsBoolFlag implements flag.boolFlag.IsBoolFlag() for wrapped klog flags. +func (k *KlogFlagVal) IsBoolFlag() bool { + if ba, ok := k.flag.Value.(boolFlag); ok { + return ba.IsBoolFlag() + } + return false +} + +// NewKlogFlagVal wraps a klog flag into KlogFlagVal type +func NewKlogFlagVal(f *flag.Flag) *KlogFlagVal { + return &KlogFlagVal{flag: f} +} + +// boolFlag replicates boolFlag interface internal to the flag package +type boolFlag interface { + IsBoolFlag() bool +} diff --git a/source/cpu/cpu.go b/source/cpu/cpu.go index 285b780ef..1bc97ba62 100644 --- a/source/cpu/cpu.go +++ b/source/cpu/cpu.go @@ -1,5 +1,5 @@ /* -Copyright 2018 The Kubernetes Authors. +Copyright 2018-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,7 +18,8 @@ package cpu import ( "io/ioutil" - "log" + + "k8s.io/klog/v2" "sigs.k8s.io/node-feature-discovery/source" "sigs.k8s.io/node-feature-discovery/source/internal/cpuidutils" @@ -96,7 +97,7 @@ func (s *Source) SetConfig(conf source.Config) { s.config = v s.initCpuidFilter() default: - log.Printf("PANIC: invalid config type: %T", conf) + klog.Fatalf("invalid config type: %T", conf) } } @@ -106,7 +107,7 @@ func (s *Source) Discover() (source.Features, error) { // Check if hyper-threading seems to be enabled found, err := haveThreadSiblings() if err != nil { - log.Printf("ERROR: failed to detect hyper-threading: %v", err) + klog.Errorf("failed to detect hyper-threading: %v", err) } else if found { features["hardware_multithreading"] = true } @@ -114,7 +115,7 @@ func (s *Source) Discover() (source.Features, error) { // Check SST-BF found, err = discoverSSTBF() if err != nil { - log.Printf("ERROR: failed to detect SST-BF: %v", err) + klog.Errorf("failed to detect SST-BF: %v", err) } else if found { features["power.sst_bf.enabled"] = true } @@ -130,7 +131,7 @@ func (s *Source) Discover() (source.Features, error) { // Detect pstate features pstate, err := detectPstate() if err != nil { - log.Printf("ERROR: %v", err) + klog.Error(err) } else { for k, v := range pstate { features["pstate."+k] = v diff --git a/source/custom/custom.go b/source/custom/custom.go index a49ad510e..1d0f74bac 100644 --- a/source/custom/custom.go +++ b/source/custom/custom.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Kubernetes Authors. +Copyright 2020-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,9 +17,10 @@ limitations under the License. package custom import ( - "log" "reflect" + "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/source" "sigs.k8s.io/node-feature-discovery/source/custom/rules" ) @@ -67,7 +68,7 @@ func (s *Source) SetConfig(conf source.Config) { case *config: s.config = v default: - log.Printf("PANIC: invalid config type: %T", conf) + klog.Fatalf("invalid config type: %T", conf) } } @@ -76,12 +77,12 @@ func (s Source) Discover() (source.Features, error) { features := source.Features{} allFeatureConfig := append(getStaticFeatureConfig(), *s.config...) allFeatureConfig = append(allFeatureConfig, getDirectoryFeatureConfig()...) - log.Printf("INFO: Custom features: %+v", allFeatureConfig) + klog.V(1).Infof("Custom features: %+v", allFeatureConfig) // Iterate over features for _, customFeature := range allFeatureConfig { featureExist, err := s.discoverFeature(customFeature) if err != nil { - log.Printf("ERROR: failed to discover feature: %q: %s", customFeature.Name, err.Error()) + klog.Errorf("failed to discover feature: %q: %s", customFeature.Name, err.Error()) continue } if featureExist { diff --git a/source/custom/directory_features.go b/source/custom/directory_features.go index 333547a55..f2aa2f01e 100644 --- a/source/custom/directory_features.go +++ b/source/custom/directory_features.go @@ -18,11 +18,11 @@ package custom import ( "io/ioutil" - "log" "os" "path/filepath" "strings" + "k8s.io/klog/v2" "sigs.k8s.io/yaml" ) @@ -32,20 +32,20 @@ const Directory = "/etc/kubernetes/node-feature-discovery/custom.d" // host directory and its 1st level subdirectories, which can be populated e.g. by ConfigMaps func getDirectoryFeatureConfig() []FeatureSpec { features := readDir(Directory, true) - //log.Printf("DEBUG: all configmap based custom feature specs: %+v", features) + klog.V(1).Infof("all configmap based custom feature specs: %+v", features) return features } func readDir(dirName string, recursive bool) []FeatureSpec { features := make([]FeatureSpec, 0) - log.Printf("DEBUG: getting files in %s", dirName) + klog.V(1).Infof("getting files in %s", dirName) files, err := ioutil.ReadDir(dirName) if err != nil { if os.IsNotExist(err) { - log.Printf("DEBUG: custom config directory %q does not exist", dirName) + klog.V(1).Infof("custom config directory %q does not exist", dirName) } else { - log.Printf("ERROR: unable to access custom config directory %q, %v", dirName, err) + klog.Errorf("unable to access custom config directory %q, %v", dirName, err) } return features } @@ -55,30 +55,30 @@ func readDir(dirName string, recursive bool) []FeatureSpec { if file.IsDir() { if recursive { - //log.Printf("DEBUG: going into dir %q", fileName) + klog.V(1).Infof("processing dir %q", fileName) features = append(features, readDir(fileName, false)...) - //} else { - // log.Printf("DEBUG: skipping dir %q", fileName) + } else { + klog.V(2).Infof("skipping dir %q", fileName) } continue } if strings.HasPrefix(file.Name(), ".") { - //log.Printf("DEBUG: skipping hidden file %q", fileName) + klog.V(2).Infof("skipping hidden file %q", fileName) continue } - //log.Printf("DEBUG: processing file %q", fileName) + klog.V(2).Infof("processing file %q", fileName) bytes, err := ioutil.ReadFile(fileName) if err != nil { - log.Printf("ERROR: could not read custom config file %q, %v", fileName, err) + klog.Errorf("could not read custom config file %q, %v", fileName, err) continue } - //log.Printf("DEBUG: custom config rules raw: %s", string(bytes)) + klog.V(2).Infof("custom config rules raw: %s", string(bytes)) config := &[]FeatureSpec{} err = yaml.UnmarshalStrict(bytes, config) if err != nil { - log.Printf("ERROR: could not parse custom config file %q, %v", fileName, err) + klog.Errorf("could not parse custom config file %q, %v", fileName, err) continue } diff --git a/source/custom/rules/nodename_rule.go b/source/custom/rules/nodename_rule.go index 744f6dd6c..7c32d9b36 100644 --- a/source/custom/rules/nodename_rule.go +++ b/source/custom/rules/nodename_rule.go @@ -16,9 +16,10 @@ limitations under the License. package rules import ( - "log" "os" "regexp" + + "k8s.io/klog/v2" ) var ( @@ -33,17 +34,17 @@ var _ Rule = NodenameRule{} func (n NodenameRule) Match() (bool, error) { for _, nodenamePattern := range n { - log.Printf("DEBUG: matchNodename %s", nodenamePattern) + klog.V(1).Infof("matchNodename %s", nodenamePattern) match, err := regexp.MatchString(nodenamePattern, nodeName) if err != nil { - log.Printf("ERROR: nodename rule: invalid nodename regexp %q: %v", nodenamePattern, err) + klog.Errorf("nodename rule: invalid nodename regexp %q: %v", nodenamePattern, err) continue } if !match { - //log.Printf("DEBUG: nodename rule: No match for pattern %q with node %q", nodenamePattern, nodeName) + klog.V(2).Infof("nodename rule: No match for pattern %q with node %q", nodenamePattern, nodeName) continue } - //log.Printf("DEBUG: nodename rule: Match for pattern %q with node %q", nodenamePattern, nodeName) + klog.V(2).Infof("nodename rule: Match for pattern %q with node %q", nodenamePattern, nodeName) return true, nil } return false, nil diff --git a/source/internal/kernelutils/kernel_kconfig.go b/source/internal/kernelutils/kernel_kconfig.go index 9d8187d28..6b56f6f2a 100644 --- a/source/internal/kernelutils/kernel_kconfig.go +++ b/source/internal/kernelutils/kernel_kconfig.go @@ -1,5 +1,5 @@ /* -Copyright 2018-2020 The Kubernetes Authors. +Copyright 2018-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,12 +21,13 @@ import ( "compress/gzip" "fmt" "io/ioutil" - "log" "os" "path/filepath" "regexp" "strings" + "k8s.io/klog/v2" + "k8s.io/apimachinery/pkg/util/validation" "sigs.k8s.io/node-feature-discovery/source" ) @@ -109,7 +110,7 @@ func ParseKconfig(configPath string) (map[string]string, error) { } else { value := strings.Trim(m[2], `"`) if len(value) > validation.LabelValueMaxLength { - log.Printf("WARNING: ignoring kconfig option '%s': value exceeds max length of %d characters", m[1], validation.LabelValueMaxLength) + klog.Warningf("ignoring kconfig option '%s': value exceeds max length of %d characters", m[1], validation.LabelValueMaxLength) continue } kconfig[m[1]] = value diff --git a/source/internal/pci_utils.go b/source/internal/pci_utils.go index 067b2a8ec..3440ad4bf 100644 --- a/source/internal/pci_utils.go +++ b/source/internal/pci_utils.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Kubernetes Authors. +Copyright 2020-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,10 +19,11 @@ package busutils import ( "fmt" "io/ioutil" - "log" "path" "strings" + "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/source" ) @@ -88,7 +89,7 @@ func DetectPci(deviceAttrSpec map[string]bool) (map[string][]PciDeviceInfo, erro for _, device := range devices { info, err := readPciDevInfo(path.Join(sysfsBasePath, device.Name()), deviceAttrSpec) if err != nil { - log.Print(err) + klog.Error(err) continue } class := info["class"] diff --git a/source/internal/usb_utils.go b/source/internal/usb_utils.go index 9bafdf93b..e2b4e7971 100644 --- a/source/internal/usb_utils.go +++ b/source/internal/usb_utils.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Kubernetes Authors. +Copyright 2020-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,10 +19,11 @@ package busutils import ( "fmt" "io/ioutil" - "log" "path" "path/filepath" "strings" + + "k8s.io/klog/v2" ) type UsbDeviceInfo map[string]string @@ -125,7 +126,7 @@ func DetectUsb(deviceAttrSpec map[string]bool) (map[string][]UsbDeviceInfo, erro for _, device := range devices { devMap, err := readUsbDevInfo(filepath.Dir(device), deviceAttrSpec) if err != nil { - log.Print(err) + klog.Error(err) continue } diff --git a/source/kernel/kernel.go b/source/kernel/kernel.go index c6d4fce02..394d84a4a 100644 --- a/source/kernel/kernel.go +++ b/source/kernel/kernel.go @@ -1,5 +1,5 @@ /* -Copyright 2018 The Kubernetes Authors. +Copyright 2018-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,10 +17,11 @@ limitations under the License. package kernel import ( - "log" "regexp" "strings" + "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/source" "sigs.k8s.io/node-feature-discovery/source/internal/kernelutils" ) @@ -63,7 +64,7 @@ func (s *Source) SetConfig(conf source.Config) { case *Config: s.config = v default: - log.Printf("PANIC: invalid config type: %T", conf) + klog.Fatalf("invalid config type: %T", conf) } } @@ -73,7 +74,7 @@ func (s *Source) Discover() (source.Features, error) { // Read kernel version version, err := parseVersion() if err != nil { - log.Printf("ERROR: Failed to get kernel version: %s", err) + klog.Errorf("Failed to get kernel version: %s", err) } else { for key := range version { features["version."+key] = version[key] @@ -83,7 +84,7 @@ func (s *Source) Discover() (source.Features, error) { // Read kconfig kconfig, err := kernelutils.ParseKconfig(s.config.KconfigFile) if err != nil { - log.Printf("ERROR: Failed to read kconfig: %s", err) + klog.Errorf("Failed to read kconfig: %s", err) } // Check flags @@ -95,7 +96,7 @@ func (s *Source) Discover() (source.Features, error) { selinux, err := SelinuxEnabled() if err != nil { - log.Print(err) + klog.Warning(err) } else if selinux { features["selinux.enabled"] = true } diff --git a/source/local/local.go b/source/local/local.go index ade714a77..7241c9ad8 100644 --- a/source/local/local.go +++ b/source/local/local.go @@ -1,5 +1,5 @@ /* -Copyright 2018 The Kubernetes Authors. +Copyright 2018-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,12 +20,13 @@ import ( "bytes" "fmt" "io/ioutil" - "log" "os" "os/exec" "path/filepath" "strings" + "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/source" ) @@ -52,18 +53,18 @@ func (s *Source) SetConfig(source.Config) {} func (s Source) Discover() (source.Features, error) { featuresFromHooks, err := getFeaturesFromHooks() if err != nil { - log.Printf("%v", err) + klog.Error(err) } featuresFromFiles, err := getFeaturesFromFiles() if err != nil { - log.Printf("%v", err) + klog.Error(err) } // Merge features from hooks and files for k, v := range featuresFromHooks { if old, ok := featuresFromFiles[k]; ok { - log.Printf("WARNING: overriding label '%s': value changed from '%s' to '%s'", + klog.Warningf("overriding label '%s': value changed from '%s' to '%s'", k, old, v) } featuresFromFiles[k] = v @@ -110,7 +111,7 @@ func getFeaturesFromHooks() (source.Features, error) { files, err := ioutil.ReadDir(hookDir) if err != nil { if os.IsNotExist(err) { - log.Printf("ERROR: hook directory %v does not exist", hookDir) + klog.Infof("hook directory %v does not exist", hookDir) return features, nil } return features, fmt.Errorf("Unable to access %v: %v", hookDir, err) @@ -120,14 +121,14 @@ func getFeaturesFromHooks() (source.Features, error) { fileName := file.Name() lines, err := runHook(fileName) if err != nil { - log.Printf("ERROR: source local failed running hook '%v': %v", fileName, err) + klog.Errorf("source local failed running hook '%v': %v", fileName, err) continue } // Append features for k, v := range parseFeatures(lines, fileName) { if old, ok := features[k]; ok { - log.Printf("WARNING: overriding label '%s' from another hook (%s): value changed from '%s' to '%s'", + klog.Warningf("overriding label '%s' from another hook (%s): value changed from '%s' to '%s'", k, fileName, old, v) } features[k] = v @@ -144,7 +145,7 @@ func runHook(file string) ([][]byte, error) { path := filepath.Join(hookDir, file) filestat, err := os.Stat(path) if err != nil { - log.Printf("ERROR: skipping %v, failed to get stat: %v", path, err) + klog.Errorf("skipping %v, failed to get stat: %v", path, err) return lines, err } @@ -165,7 +166,7 @@ func runHook(file string) ([][]byte, error) { // Don't print the last empty string break } - log.Printf("%v: %s", file, line) + klog.Errorf("%v: %s", file, line) } // Do not return any lines if an error occurred @@ -185,7 +186,7 @@ func getFeaturesFromFiles() (source.Features, error) { files, err := ioutil.ReadDir(featureFilesDir) if err != nil { if os.IsNotExist(err) { - log.Printf("ERROR: features directory %v does not exist", featureFilesDir) + klog.Infof("features directory %v does not exist", featureFilesDir) return features, nil } return features, fmt.Errorf("Unable to access %v: %v", featureFilesDir, err) @@ -195,14 +196,14 @@ func getFeaturesFromFiles() (source.Features, error) { fileName := file.Name() lines, err := getFileContent(fileName) if err != nil { - log.Printf("ERROR: source local failed reading file '%v': %v", fileName, err) + klog.Errorf("source local failed reading file '%v': %v", fileName, err) continue } // Append features for k, v := range parseFeatures(lines, fileName) { if old, ok := features[k]; ok { - log.Printf("WARNING: overriding label '%s' from another features.d file (%s): value changed from '%s' to '%s'", + klog.Warningf("overriding label '%s' from another features.d file (%s): value changed from '%s' to '%s'", k, fileName, old, v) } features[k] = v @@ -219,7 +220,7 @@ func getFileContent(fileName string) ([][]byte, error) { path := filepath.Join(featureFilesDir, fileName) filestat, err := os.Stat(path) if err != nil { - log.Printf("ERROR: skipping %v, failed to get stat: %v", path, err) + klog.Errorf("skipping %v, failed to get stat: %v", path, err) return lines, err } diff --git a/source/memory/memory.go b/source/memory/memory.go index 2e367d698..87bac0952 100644 --- a/source/memory/memory.go +++ b/source/memory/memory.go @@ -1,5 +1,5 @@ /* -Copyright 2018 The Kubernetes Authors. +Copyright 2018-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,10 +18,11 @@ package memory import ( "io/ioutil" - "log" "os" "strings" + "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/source" ) @@ -47,7 +48,7 @@ func (s Source) Discover() (source.Features, error) { // Detect NUMA numa, err := isNuma() if err != nil { - log.Printf("ERROR: failed to detect NUMA topology: %s", err) + klog.Errorf("failed to detect NUMA topology: %s", err) } else if numa { features["numa"] = true } @@ -55,7 +56,7 @@ func (s Source) Discover() (source.Features, error) { // Detect NVDIMM nv, err := detectNvdimm() if err != nil { - log.Printf("ERROR: NVDIMM detection failed: %s", err) + klog.Errorf("NVDIMM detection failed: %s", err) } else { for k, v := range nv { features["nv."+k] = v @@ -110,7 +111,7 @@ func detectNvdimm() (map[string]bool, error) { } } } else { - log.Printf("WARNING: failed to detect NVDIMM configuration: %s", err) + klog.Warningf("failed to detect NVDIMM configuration: %s", err) } return features, nil diff --git a/source/network/network.go b/source/network/network.go index 4e15b9c73..7baba5eea 100644 --- a/source/network/network.go +++ b/source/network/network.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +Copyright 2017-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,10 +20,11 @@ import ( "bytes" "fmt" "io/ioutil" - "log" "strconv" "strings" + "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/source" ) @@ -66,43 +67,43 @@ func (s Source) Discover() (source.Features, error) { name := netInterface.Name() flags, err := readIfFlags(name) if err != nil { - log.Printf("%v", err) + klog.Error(err) continue } if flags&flagUp != 0 && flags&flagLoopback == 0 { totalBytes, err := ioutil.ReadFile(source.SysfsDir.Path(sysfsBaseDir, name, "device/sriov_totalvfs")) if err != nil { - log.Printf("SR-IOV not supported for network interface: %s: %v", name, err) + klog.V(1).Infof("SR-IOV not supported for network interface: %s: %v", name, err) continue } total := bytes.TrimSpace(totalBytes) t, err := strconv.Atoi(string(total)) if err != nil { - log.Printf("Error in obtaining maximum supported number of virtual functions for network interface: %s: %v", name, err) + klog.Errorf("Error in obtaining maximum supported number of virtual functions for network interface: %s: %v", name, err) continue } if t > 0 { - log.Printf("SR-IOV capability is detected on the network interface: %s", name) - log.Printf("%d maximum supported number of virtual functions on network interface: %s", t, name) + klog.V(1).Infof("SR-IOV capability is detected on the network interface: %s", name) + klog.V(1).Infof("%d maximum supported number of virtual functions on network interface: %s", t, name) features["sriov.capable"] = true numBytes, err := ioutil.ReadFile(source.SysfsDir.Path(sysfsBaseDir, name, "device/sriov_numvfs")) if err != nil { - log.Printf("SR-IOV not configured for network interface: %s: %s", name, err) + klog.V(1).Infof("SR-IOV not configured for network interface: %s: %s", name, err) continue } num := bytes.TrimSpace(numBytes) n, err := strconv.Atoi(string(num)) if err != nil { - log.Printf("Error in obtaining the configured number of virtual functions for network interface: %s: %v", name, err) + klog.Errorf("Error in obtaining the configured number of virtual functions for network interface: %s: %v", name, err) continue } if n > 0 { - log.Printf("%d virtual functions configured on network interface: %s", n, name) + klog.V(1).Infof("%d virtual functions configured on network interface: %s", n, name) features["sriov.configured"] = true break } else if n == 0 { - log.Printf("SR-IOV not configured on network interface: %s", name) + klog.V(1).Infof("SR-IOV not configured on network interface: %s", name) } } } diff --git a/source/pci/pci.go b/source/pci/pci.go index d6cf751a6..56b290f13 100644 --- a/source/pci/pci.go +++ b/source/pci/pci.go @@ -1,5 +1,5 @@ /* -Copyright 2018 The Kubernetes Authors. +Copyright 2018-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,9 +18,10 @@ package pci import ( "fmt" - "log" "strings" + "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/source" pciutils "sigs.k8s.io/node-feature-discovery/source/internal" ) @@ -58,7 +59,7 @@ func (s *Source) SetConfig(conf source.Config) { case *Config: s.config = v default: - log.Printf("PANIC: invalid config type: %T", conf) + klog.Fatalf("invalid config type: %T", conf) } } @@ -84,10 +85,10 @@ func (s Source) Discover() (source.Features, error) { for key := range configLabelFields { keys = append(keys, key) } - log.Printf("WARNING: invalid fields '%v' in deviceLabelFields, ignoring...", keys) + klog.Warningf("invalid fields '%v' in deviceLabelFields, ignoring...", keys) } if len(deviceLabelFields) == 0 { - log.Printf("WARNING: no valid fields in deviceLabelFields defined, using the defaults") + klog.Warningf("no valid fields in deviceLabelFields defined, using the defaults") deviceLabelFields = []string{"class", "vendor"} } diff --git a/source/system/system.go b/source/system/system.go index 6d6819edc..1633dc1cf 100644 --- a/source/system/system.go +++ b/source/system/system.go @@ -1,5 +1,5 @@ /* -Copyright 2018 The Kubernetes Authors. +Copyright 2018-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,11 +18,12 @@ package system import ( "bufio" - "log" "os" "regexp" "strings" + "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/source" ) @@ -50,7 +51,7 @@ func (s Source) Discover() (source.Features, error) { release, err := parseOSRelease() if err != nil { - log.Printf("ERROR: failed to get os-release: %s", err) + klog.Errorf("failed to get os-release: %s", err) } else { for _, key := range osReleaseFields { if value, exists := release[key]; exists { diff --git a/source/usb/usb.go b/source/usb/usb.go index 3b50ebabe..2cafcffd0 100644 --- a/source/usb/usb.go +++ b/source/usb/usb.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Kubernetes Authors. +Copyright 2020-2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,9 +18,10 @@ package usb import ( "fmt" - "log" "strings" + "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/source" usbutils "sigs.k8s.io/node-feature-discovery/source/internal" ) @@ -61,7 +62,7 @@ func (s *Source) SetConfig(conf source.Config) { case *Config: s.config = v default: - log.Printf("PANIC: invalid config type: %T", conf) + klog.Fatalf("invalid config type: %T", conf) } } @@ -87,10 +88,10 @@ func (s Source) Discover() (source.Features, error) { for key := range configLabelFields { keys = append(keys, key) } - log.Printf("WARNING: invalid fields '%v' in deviceLabelFields, ignoring...", keys) + klog.Warningf("invalid fields '%v' in deviceLabelFields, ignoring...", keys) } if len(deviceLabelFields) == 0 { - log.Printf("WARNING: no valid fields in deviceLabelFields defined, using the defaults") + klog.Warningf("no valid fields in deviceLabelFields defined, using the defaults") deviceLabelFields = []string{"vendor", "device"} }