diff --git a/cmd/noderesourcetopology-plugin/main.go b/cmd/noderesourcetopology-plugin/main.go index efaa9ba749..20beed446b 100644 --- a/cmd/noderesourcetopology-plugin/main.go +++ b/cmd/noderesourcetopology-plugin/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "context" "math/rand" "os" "time" @@ -33,6 +34,7 @@ import ( _ "sigs.k8s.io/scheduler-plugins/apis/config/scheme" knifeatures "sigs.k8s.io/scheduler-plugins/pkg-kni/features" + knilogger "sigs.k8s.io/scheduler-plugins/pkg-kni/logger" knistatus "sigs.k8s.io/scheduler-plugins/pkg-kni/pfpstatus" ) @@ -41,8 +43,11 @@ func main() { rand.Seed(time.Now().UnixNano()) - logh := klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog)) - + logh, ok := knilogger.Setup(context.Background()) + if !ok { + // KNI logger disabled + logh = klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog)) + } knistatus.Setup(logh) // Register custom plugins to the scheduler framework. diff --git a/go.mod b/go.mod index 3b953271d7..5420315f5b 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/diktyo-io/networktopology-api v1.0.1-alpha github.com/dustin/go-humanize v1.0.1 github.com/go-logr/logr v1.2.4 + github.com/go-logr/stdr v1.2.2 github.com/google/go-cmp v0.5.9 github.com/k8stopologyawareschedwg/noderesourcetopology-api v0.1.1 github.com/k8stopologyawareschedwg/podfingerprint v0.2.2 @@ -51,7 +52,6 @@ require ( github.com/felixge/httpsnoop v1.0.3 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect diff --git a/pkg-kni/logger/dump.go b/pkg-kni/logger/dump.go new file mode 100644 index 0000000000..3bb26e3725 --- /dev/null +++ b/pkg-kni/logger/dump.go @@ -0,0 +1,90 @@ +/* + * Copyright 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +import ( + "bytes" + "context" + "os" + "path/filepath" + "strings" + "time" + + "github.com/go-logr/logr" +) + +func RunForever(ctx context.Context, logger logr.Logger, interval time.Duration, baseDirectory string, lc *LogCache) { + // let's try to keep the amount of code we do in init() at minimum. + // This may happen if the container didn't have the directory mounted + discard := !existsBaseDirectory(baseDirectory) + if discard { + logger.Info("base directory not found, will discard everything", "baseDirectory", baseDirectory) + } + + delta := interval - 10*time.Millisecond // TODO + logger.Info("dump loop info", "interval", interval, "delta", delta) + + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case now := <-ticker.C: + // keep the size at bay by popping old data even if we just discard it + expireds := lc.PopExpired(now, delta) + for _, expired := range expireds { + if discard { + continue + } + // intentionally swallow error. + // - if we hit issues and we do log (V=2, like), we will clog the regular log + // - if we hit issues and we do NOT log (v=5, like) we will not see it anyway + DumpLogNode(baseDirectory, expired.logID, expired.data) + } + if len(expireds) > 0 { + logger.V(4).Info("written logs to storage", "entries", len(expireds)) + } + } + } +} + +func DumpLogNode(statusDir string, logName string, data bytes.Buffer) error { + logName = fixLogName(logName) + dst, err := os.OpenFile(filepath.Join(statusDir, logName+".log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + return err + } + if _, err := dst.Write(data.Bytes()); err != nil { + dst.Close() // swallow error because we want to bubble up the write error + return err + } + return dst.Close() +} + +func existsBaseDirectory(baseDir string) bool { + info, err := os.Stat(baseDir) + if err != nil { + return false + } + return info.IsDir() +} + +func fixLogName(name string) string { + return strings.ReplaceAll(name, "/", "__") +} diff --git a/pkg-kni/logger/dump_test.go b/pkg-kni/logger/dump_test.go new file mode 100644 index 0000000000..be7ede4310 --- /dev/null +++ b/pkg-kni/logger/dump_test.go @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +import ( + "bytes" + "os" + "path/filepath" + "testing" +) + +func TestDumpLogNodeAppendsData(t *testing.T) { + dir, err := os.MkdirTemp("", "kni-logger-dump-data") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) // clean up + + buf1 := bytes.NewBufferString("fizzbuzz\n") + err = DumpLogNode(dir, "foo-test", *buf1) + if err != nil { + t.Fatal(err) + } + + buf2 := bytes.NewBufferString("foobar\n") + err = DumpLogNode(dir, "foo-test", *buf2) + if err != nil { + t.Fatal(err) + } + + data, err := os.ReadFile(filepath.Join(dir, "foo-test.log")) + if err != nil { + t.Fatal(err) + } + + got := string(data) + expected := "fizzbuzz\nfoobar\n" + if got != expected { + t.Errorf("read error\ngot=[%s]\nexp=[%s]", got, expected) + } +} diff --git a/pkg-kni/logger/knilogr.go b/pkg-kni/logger/knilogr.go new file mode 100644 index 0000000000..94d2964fc4 --- /dev/null +++ b/pkg-kni/logger/knilogr.go @@ -0,0 +1,113 @@ +/* + * Copyright 2019 The logr Authors. + * Copyright 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Derived from https://github.com/go-logr/stdr/blob/v1.2.2/stdr.go + */ + +package logger + +import ( + "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" + "github.com/go-logr/stdr" +) + +type knilogger struct { + funcr.Formatter + std stdr.StdLogger + cache *LogCache + logID string + hasLogID bool + verbose int +} + +func New(std stdr.StdLogger, lc *LogCache, verbose int, opts stdr.Options) logr.Logger { + sl := &knilogger{ + Formatter: funcr.NewFormatter(funcr.Options{ + LogCaller: funcr.MessageClass(opts.LogCaller), + }), + std: std, + cache: lc, + verbose: verbose, + } + + // For skipping our own logger.Info/Error. + sl.Formatter.AddCallDepth(1 + opts.Depth) + + return logr.New(sl) +} + +func (l knilogger) Enabled(level int) bool { + return l.verbose >= level || l.logID != "" +} + +func (l knilogger) Info(level int, msg string, kvList ...interface{}) { + prefix, args := l.FormatInfo(level, msg, kvList) + if prefix != "" { + args = prefix + ": " + args + } + if l.cache != nil { + logID, ok := l.logID, l.hasLogID + if !ok { + logID, ok = StartsWithLogID(kvList...) + } + if ok { + l.cache.Put(logID, args) // ignore error intentionally + } + } + // because we can be here because either we have enough verbosiness + // OR because stored logID. So we must redo this check. + if l.verbose < level { + return + } + _ = l.std.Output(l.Formatter.GetDepth()+1, args) +} + +func (l knilogger) Error(err error, msg string, kvList ...interface{}) { + prefix, args := l.FormatError(err, msg, kvList) + if prefix != "" { + args = prefix + ": " + args + } + if l.cache != nil { + logID, ok := l.logID, l.hasLogID + if !ok { + logID, ok = StartsWithLogID(kvList...) + } + if ok { + l.cache.Put(logID, args) // ignore error intentionally + } + } + _ = l.std.Output(l.Formatter.GetDepth()+1, args) +} + +func (l knilogger) WithName(name string) logr.LogSink { + l.Formatter.AddName(name) + return &l +} + +func (l knilogger) WithValues(kvList ...interface{}) logr.LogSink { + l.logID, l.hasLogID = FindLogID(kvList) + l.Formatter.AddValues(kvList) + return &l +} + +func (l knilogger) WithCallDepth(depth int) logr.LogSink { + l.Formatter.AddCallDepth(depth) + return &l +} + +var _ logr.LogSink = &knilogger{} +var _ logr.CallDepthLogSink = &knilogger{} diff --git a/pkg-kni/logger/logcache.go b/pkg-kni/logger/logcache.go new file mode 100644 index 0000000000..357312e075 --- /dev/null +++ b/pkg-kni/logger/logcache.go @@ -0,0 +1,101 @@ +/* + * Copyright 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +import ( + "bytes" + "sync" + "time" +) + +type LogNode struct { + logID string + data bytes.Buffer + lastUpdate time.Time +} + +func (ln *LogNode) IsExpired(now time.Time, delta time.Duration) bool { + return now.Sub(ln.lastUpdate) >= delta +} + +type TimeFunc func() time.Time + +type LogCache struct { + mutex sync.Mutex + // map logID -> data + nodes map[string]*LogNode + timeFunc TimeFunc +} + +func NewLogCache(timeFunc TimeFunc) *LogCache { + return &LogCache{ + nodes: make(map[string]*LogNode), + timeFunc: timeFunc, + } +} + +func (lc *LogCache) Put(logID, data string) error { + lc.mutex.Lock() + defer lc.mutex.Unlock() + buf := lc.bufferFor(logID) + _, err := buf.WriteString(data) + return err +} + +func (lc *LogCache) PopExpired(now time.Time, delta time.Duration) []*LogNode { + ret := []*LogNode{} + lc.mutex.Lock() + defer lc.mutex.Unlock() + for logID, LogNode := range lc.nodes { + if !LogNode.IsExpired(now, delta) { + continue + } + ret = append(ret, LogNode) + delete(lc.nodes, logID) + } + return ret +} + +// Get is (mostly) meant for testing purposes +func (lc *LogCache) Get(logID string) (string, bool) { + lc.mutex.Lock() + defer lc.mutex.Unlock() + node, ok := lc.nodes[logID] + if !ok { + return "", false + } + return node.data.String(), true +} + +// Len is (mostly) meant for testing purposes +func (lc *LogCache) Len() int { + lc.mutex.Lock() + defer lc.mutex.Unlock() + return len(lc.nodes) +} + +func (lc *LogCache) bufferFor(logID string) *bytes.Buffer { + node, ok := lc.nodes[logID] + if !ok { + node = &LogNode{ + logID: logID, + } + lc.nodes[logID] = node + } + node.lastUpdate = lc.timeFunc() + return &node.data +} diff --git a/pkg-kni/logger/logcache_test.go b/pkg-kni/logger/logcache_test.go new file mode 100644 index 0000000000..c4eb3f7ec8 --- /dev/null +++ b/pkg-kni/logger/logcache_test.go @@ -0,0 +1,122 @@ +/* + * Copyright 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +import ( + "testing" + "time" +) + +func TestEmpty(t *testing.T) { + ft := FakeTime{} + lc := NewLogCache(ft.Now) + sz := lc.Len() + if sz > 0 { + t.Errorf("unexpected len > 0: %v", sz) + } +} + +func TestPutGet(t *testing.T) { + ft := FakeTime{} + lc := NewLogCache(ft.Now) + err := lc.Put("foo", "fizzbuzz") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + got, ok := lc.Get("foo") + if !ok || got != "fizzbuzz" { + t.Fatalf("unexpected value: %v (ok=%v)", got, ok) + } +} + +func TestMultiKeyPutGet(t *testing.T) { + ft := FakeTime{} + lc := NewLogCache(ft.Now) + keys := []string{"foo", "bar", "baz", "buz", "abc"} + for _, key := range keys { + err := lc.Put(key, "fizzbuzz") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + for _, key := range keys { + got, ok := lc.Get(key) + if !ok || got != "fizzbuzz" { + t.Fatalf("unexpected value: %v (ok=%v)", got, ok) + } + } + sz := lc.Len() + if sz != len(keys) { + t.Errorf("unexpected len: %d expected: %d", sz, len(keys)) + } +} + +func TestAppendGet(t *testing.T) { + ft := FakeTime{} + lc := NewLogCache(ft.Now) + for _, data := range []string{"fizz", "buzz", "fizz", "buzz", "fizz"} { + err := lc.Put("foo", data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + got, ok := lc.Get("foo") + if !ok || got != "fizzbuzzfizzbuzzfizz" { + t.Fatalf("unexpected value: %v (ok=%v)", got, ok) + } + + sz := lc.Len() + if sz != 1 { + t.Errorf("unexpected len: %v", sz) + } +} + +func TestMultiKeyAppendGet(t *testing.T) { + ft := FakeTime{} + lc := NewLogCache(ft.Now) + keys := []string{"foo", "bar", "baz", "buz", "abc"} + expected := make(map[string]string) + + for _, data := range []string{"fizz", "buzz", "fizz", "buzz", "fizz"} { + for _, key := range keys { + err := lc.Put(key, data+key) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expected[key] += data + key + } + } + for _, key := range keys { + got, ok := lc.Get(key) + if !ok || got != expected[key] { + t.Fatalf("unexpected value for key %q: %v (ok=%v)", key, got, ok) + } + } + + sz := lc.Len() + if sz != len(keys) { + t.Errorf("unexpected len: %d expected: %d", sz, len(keys)) + } +} + +type FakeTime struct { + TS time.Time +} + +func (ft FakeTime) Now() time.Time { + return ft.TS +} diff --git a/pkg-kni/logger/logid.go b/pkg-kni/logger/logid.go new file mode 100644 index 0000000000..40615dcd38 --- /dev/null +++ b/pkg-kni/logger/logid.go @@ -0,0 +1,86 @@ +/* + * Copyright 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +const ( + logIDKey = "logID" +) + +func StartsWithLogID(kvList ...interface{}) (string, bool) { + if len(kvList) < 2 { + return "", false + } + return isLogIDPair(kvList[0], kvList[1]) +} + +func FindLogID(values []interface{}) (string, bool) { + if len(values) < 2 || len(values)%2 != 0 { + return "", false // should never happen + } + for i := 0; i < len(values); i += 2 { + if vs, ok := isLogIDPair(values[i], values[i+1]); ok { + return vs, ok + } + } + return "", false +} + +func GetLogID(values []interface{}, kvList ...interface{}) (string, bool) { + // quick check because we look at most at 2 values + if len(kvList) >= 2 { + if vs, ok := isLogIDPair(kvList[0], kvList[1]); ok { + return vs, ok + } + } + if len(values) < 2 || len(values)%2 != 0 { + return "", false // should never happen + } + for i := 0; i < len(values); i += 2 { + if vs, ok := isLogIDPair(values[i], values[i+1]); ok { + return vs, ok + } + } + return "", false +} + +func HasLogIDKey(vals []interface{}) bool { + if len(vals) < 2 || len(vals)%2 != 0 { + return false + } + for i := 0; i < len(vals); i += 2 { + if isLogIDKey(vals[i]) { + return true + } + } + return false +} + +func isLogIDPair(key, val interface{}) (string, bool) { + if !isLogIDKey(key) { + return "", false + } + vs, ok := val.(string) + return vs, ok +} + +func isLogIDKey(val interface{}) bool { + v, ok := val.(string) + if !ok { + return false + } + return v == logIDKey +} diff --git a/pkg-kni/logger/logid_test.go b/pkg-kni/logger/logid_test.go new file mode 100644 index 0000000000..7ce819c526 --- /dev/null +++ b/pkg-kni/logger/logid_test.go @@ -0,0 +1,213 @@ +/* + * Copyright 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +import ( + "testing" +) + +func TestHasLogIDKey(t *testing.T) { + tests := []struct { + name string + values []interface{} + expected bool + }{ + { + name: "nil", + values: nil, + expected: false, + }, + { + name: "empty", + values: []interface{}{}, + expected: false, + }, + { + name: "missing val", + values: []interface{}{"logID"}, + expected: false, + }, + { + name: "missing key", + values: []interface{}{"foobar"}, + expected: false, + }, + { + name: "minimal", + values: []interface{}{"logID", "FOO"}, + expected: true, + }, + { + name: "uneven", + values: []interface{}{"logID", "FOO", "BAR"}, + expected: false, + }, + { + name: "multikey", + values: []interface{}{"logID", "AAA", "fizz", "buzz"}, + expected: true, + }, + { + name: "multikey-mispell", + values: []interface{}{"logid", "AAA", "fizz", "buzz"}, + expected: false, + }, + { + name: "multikey-mistype", + values: []interface{}{"logID", 12345, "fizz", "buzz"}, + expected: true, + }, + { + name: "ok-not-first", + values: []interface{}{"foo", "bar", "logID", "AAA", "fizz", "buzz"}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := HasLogIDKey(tt.values) + if got != tt.expected { + t.Errorf("values=%v got=[%v] expected=[%v]", tt.values, got, tt.expected) + } + }) + } +} + +func TestGetLogID(t *testing.T) { + tests := []struct { + name string + values []interface{} // always even (ensured by other API contracts) + kvList []interface{} + expectedID string + expectedOK bool + }{ + { + name: "nil", + values: nil, + kvList: []interface{}{}, + expectedID: "", + expectedOK: false, + }, + { + name: "empty", + values: []interface{}{}, + kvList: []interface{}{}, + expectedID: "", + expectedOK: false, + }, + { + name: "missing val", + values: []interface{}{}, + kvList: []interface{}{"logID"}, + expectedID: "", + expectedOK: false, + }, + { + name: "missing key", + values: []interface{}{}, + kvList: []interface{}{"foobar"}, + expectedID: "", + expectedOK: false, + }, + { + name: "minimal", + values: []interface{}{}, + kvList: []interface{}{"logID", "FOO"}, + expectedID: "FOO", + expectedOK: true, + }, + { + name: "uneven", + values: []interface{}{}, + kvList: []interface{}{"logID", "FOO", "BAR"}, + // from the limited perspective of getting logID, this is OK + expectedID: "FOO", + expectedOK: true, + }, + { + name: "multikey", + values: []interface{}{}, + kvList: []interface{}{"logID", "AAA", "fizz", "buzz"}, + expectedID: "AAA", + expectedOK: true, + }, + { + name: "ok-not-first", + values: []interface{}{}, + // not the first to save search time, can be changed in the future + kvList: []interface{}{"foo", "bar", "logID", "BBB", "fizz", "buzz"}, + expectedID: "", + expectedOK: false, + }, + { + name: "missing-both", + values: []interface{}{"alpha", "1", "beta", "2"}, + // not the first to save search time, can be changed in the future + kvList: []interface{}{"foo", "bar", "fizz", "buzz"}, + expectedID: "", + expectedOK: false, + }, + { + name: "values-ok-not-first", + values: []interface{}{"alpha", "1", "logID", "BBB", "beta", "2"}, + kvList: []interface{}{"foo", "bar", "fizz", "buzz"}, + expectedID: "BBB", + expectedOK: true, + }, + { + name: "kvList-prevails", + values: []interface{}{"logID", "values", "nodeName", "localhost"}, + kvList: []interface{}{"logID", "kvList", "foo", "bar", "fizz", "buzz"}, + expectedID: "kvList", + expectedOK: true, + }, + { + name: "kvList-mislpaleced", + values: []interface{}{"first", "taken", "logID", "values", "nodeName", "localhost"}, + kvList: []interface{}{"useless", "value", "logID", "kvList", "foo", "bar", "fizz", "buzz"}, + expectedID: "values", + expectedOK: true, + }, + { + name: "kvList-mistype", + values: []interface{}{"alpha", "1", "beta", "2"}, + kvList: []interface{}{"logID", 12345, "fizz", "buzz"}, + expectedID: "", + expectedOK: false, + }, + { + name: "kvList-mistype-full", + values: []interface{}{"alpha", "1", "beta", "2"}, + kvList: []interface{}{123, 45, "fizz", "buzz"}, + expectedID: "", + expectedOK: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := GetLogID(tt.values, tt.kvList...) + if ok != tt.expectedOK { + t.Fatalf("values=%v kvList=%v OK got=[%v] expected=[%v]", tt.values, tt.kvList, ok, tt.expectedOK) + } + if got != tt.expectedID { + t.Errorf("values=%v kvList=%v ID got=[%v] expected=[%v]", tt.values, tt.kvList, got, tt.expectedID) + } + }) + } +} diff --git a/pkg-kni/logger/setup.go b/pkg-kni/logger/setup.go new file mode 100644 index 0000000000..1d036ed2fd --- /dev/null +++ b/pkg-kni/logger/setup.go @@ -0,0 +1,80 @@ +/* + * Copyright 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +import ( + "context" + "log" + "os" + "strconv" + "time" + + "github.com/go-logr/logr" + "github.com/go-logr/stdr" + "k8s.io/klog/v2" +) + +const ( + KNILoggerDirEnvVar string = "KNI_LOGGER_DUMP_DIR" + KNILoggerIntervalEnvVar string = "KNI_LOGGER_DUMP_INTERVAL" + KNILoggerVerboseEnvVar string = "KNI_LOGGER_VERBOSE" +) + +func Setup(ctx context.Context) (logr.Logger, bool) { + backend := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile) + logh := stdr.New(backend) + + dumpDir, ok := os.LookupEnv(KNILoggerDirEnvVar) + if !ok || dumpDir == "" { + logh.Info("disabled", "reason", "directory", "variableFound", ok, "valueGiven", dumpDir != "") + return logh, false + } + + val, ok := os.LookupEnv(KNILoggerIntervalEnvVar) + if !ok { + logh.Info("disabled", "reason", "interval", "variableFound", ok) + return logh, false + } + interval, err := time.ParseDuration(val) + if err != nil { + logh.Info("cannot parse", "interval", val, "error", err) + return logh, false + } + if interval == 0 { + return logh, false + } + + verb, ok := os.LookupEnv(KNILoggerVerboseEnvVar) + if !ok { + logh.Info("disabled", "reason", "verbose", "variableFound", ok) + return logh, false + } + verbose, err := strconv.Atoi(verb) + if err != nil { + logh.Info("cannot parse", "verbose", verb, "error", err) + return logh, false + } + + logh.Info("setting up done", "verbose", verbose, "interval", interval, "directory", dumpDir) + + cache := NewLogCache(time.Now) + + klog.SetLogger(New(backend, cache, verbose, stdr.Options{})) + go RunForever(ctx, logh, interval, dumpDir, cache) + + return logh, true +}