From 27e3d661c5201803736ec695967b00b0e0050cb6 Mon Sep 17 00:00:00 2001 From: Evan Baker Date: Fri, 21 Feb 2025 00:08:32 +0000 Subject: [PATCH 1/3] feat: add migration shim and config for v2 logger Signed-off-by: Evan Baker Date: Fri, 21 Feb 2025 21:15:04 +0000 Subject: [PATCH 2/3] add logger config to CNS config Signed-off-by: Evan Baker --- cns/configuration/configuration.go | 2 ++ cns/service/main.go | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cns/configuration/configuration.go b/cns/configuration/configuration.go index e14c7a4c4b..e468b39d6a 100644 --- a/cns/configuration/configuration.go +++ b/cns/configuration/configuration.go @@ -10,6 +10,7 @@ import ( "github.com/Azure/azure-container-networking/cns" "github.com/Azure/azure-container-networking/cns/logger" + loggerv2 "github.com/Azure/azure-container-networking/cns/logger/v2" "github.com/Azure/azure-container-networking/common" "github.com/pkg/errors" ) @@ -37,6 +38,7 @@ type CNSConfig struct { EnableSwiftV2 bool InitializeFromCNI bool KeyVaultSettings KeyVaultSettings + Logger loggerv2.Config MSISettings MSISettings ManageEndpointState bool ManagedSettings ManagedSettings diff --git a/cns/service/main.go b/cns/service/main.go index 9776ba6bbc..77c1e42bc1 100644 --- a/cns/service/main.go +++ b/cns/service/main.go @@ -67,6 +67,7 @@ import ( "github.com/Azure/azure-container-networking/store" "github.com/Azure/azure-container-networking/telemetry" "github.com/avast/retry-go/v4" + "github.com/go-logr/zapr" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" "go.uber.org/zap" @@ -84,7 +85,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/healthz" - ctrlzap "sigs.k8s.io/controller-runtime/pkg/log/zap" ctrlmgr "sigs.k8s.io/controller-runtime/pkg/manager" ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics/server" ) @@ -629,7 +629,7 @@ func main() { } // build the zap logger - z, c, err := loggerv2.New(&loggerv2.Config{}) + z, c, err := loggerv2.New(&cnsconfig.Logger) defer c() if err != nil { fmt.Printf("failed to create logger: %v", err) @@ -1515,7 +1515,7 @@ func InitializeCRDState(ctx context.Context, z *zap.Logger, httpRestService cns. Scheme: scheme, Metrics: ctrlmetrics.Options{BindAddress: "0"}, Cache: cacheOpts, - Logger: ctrlzap.New(), + Logger: zapr.NewLogger(z), } manager, err := ctrl.NewManager(kubeConfig, managerOpts) From f643a2d065c4d8fdba14a0f74bab3a8444e28280 Mon Sep 17 00:00:00 2001 From: Evan Baker Date: Fri, 28 Feb 2025 04:01:51 +0000 Subject: [PATCH 3/3] map telemetry metadata fields and compose logger Signed-off-by: Evan Baker --- aitelemetry/telemetrywrapper.go | 11 +++++--- aitelemetry/telemetrywrapper_linux.go | 5 ---- aitelemetry/telemetrywrapper_windows.go | 8 ------ cns/logger/v2/config.go | 4 +-- cns/logger/v2/cores/ai.go | 2 +- cns/logger/v2/cores/etw_windows.go | 34 ++++++++++++++++++++++--- cns/logger/v2/cores/file.go | 11 ++++---- cns/logger/v2/cores/stdout.go | 29 +++++++++++++++++++++ cns/logger/v2/fields.go | 23 +++++++++++++++++ cns/logger/v2/logger.go | 1 + cns/logger/v2/logger_linux.go | 2 +- cns/logger/v2/logger_windows.go | 2 +- cns/service/main.go | 16 ++++++++++-- 13 files changed, 115 insertions(+), 33 deletions(-) delete mode 100644 aitelemetry/telemetrywrapper_linux.go delete mode 100644 aitelemetry/telemetrywrapper_windows.go create mode 100644 cns/logger/v2/fields.go diff --git a/aitelemetry/telemetrywrapper.go b/aitelemetry/telemetrywrapper.go index 06a134d2d2..39deebb802 100644 --- a/aitelemetry/telemetrywrapper.go +++ b/aitelemetry/telemetrywrapper.go @@ -3,6 +3,7 @@ package aitelemetry import ( "fmt" "os" + "path/filepath" "runtime" "time" @@ -36,6 +37,8 @@ const ( defaultRefreshTimeoutInSecs = 10 ) +var MetadataFile = filepath.Join(os.TempDir(), "azuremetadata.json") + type Level = contracts.SeverityLevel const ( @@ -98,7 +101,7 @@ func getMetadata(th *telemetryHandle) { // check if metadata in memory otherwise initiate wireserver request for { - metadata, err = common.GetHostMetadata(metadataFile) + metadata, err = common.GetHostMetadata(MetadataFile) if err == nil || th.disableMetadataRefreshThread { break } @@ -117,14 +120,14 @@ func getMetadata(th *telemetryHandle) { th.metadata = metadata th.rwmutex.Unlock() - lockclient, err := processlock.NewFileLock(metadataFile + store.LockExtension) + lockclient, err := processlock.NewFileLock(MetadataFile + store.LockExtension) if err != nil { log.Printf("Error initializing file lock:%v", err) return } // Save metadata retrieved from wireserver to a file - kvs, err := store.NewJsonFileStore(metadataFile, lockclient, nil) + kvs, err := store.NewJsonFileStore(MetadataFile, lockclient, nil) if err != nil { debugLog("[AppInsights] Error initializing kvs store: %v", err) return @@ -134,7 +137,7 @@ func getMetadata(th *telemetryHandle) { log.Errorf("getMetadata: Not able to acquire lock:%v", err) return } - metadataErr := common.SaveHostMetadata(th.metadata, metadataFile) + metadataErr := common.SaveHostMetadata(th.metadata, MetadataFile) err = kvs.Unlock() if err != nil { log.Errorf("getMetadata: Not able to release lock:%v", err) diff --git a/aitelemetry/telemetrywrapper_linux.go b/aitelemetry/telemetrywrapper_linux.go deleted file mode 100644 index 17bf74fc7c..0000000000 --- a/aitelemetry/telemetrywrapper_linux.go +++ /dev/null @@ -1,5 +0,0 @@ -package aitelemetry - -const ( - metadataFile = "/tmp/azuremetadata.json" -) diff --git a/aitelemetry/telemetrywrapper_windows.go b/aitelemetry/telemetrywrapper_windows.go deleted file mode 100644 index cd49357705..0000000000 --- a/aitelemetry/telemetrywrapper_windows.go +++ /dev/null @@ -1,8 +0,0 @@ -package aitelemetry - -import ( - "os" - "path/filepath" -) - -var metadataFile = filepath.FromSlash(os.Getenv("TEMP")) + "\\azuremetadata.json" diff --git a/cns/logger/v2/config.go b/cns/logger/v2/config.go index d64d6486fa..5f4a095fb3 100644 --- a/cns/logger/v2/config.go +++ b/cns/logger/v2/config.go @@ -42,8 +42,8 @@ func (c *Config) UnmarshalJSON(data []byte) error { return nil } -// Normalize checks the Config for missing or illegal values and sets them -// to defaults if appropriate. +// Normalize checks the Config for missing/default values and sets them +// if appropriate. func (c *Config) Normalize() { if c.File != nil { if c.File.Filepath == "" { diff --git a/cns/logger/v2/cores/ai.go b/cns/logger/v2/cores/ai.go index 76ba6add65..d7cc82b801 100644 --- a/cns/logger/v2/cores/ai.go +++ b/cns/logger/v2/cores/ai.go @@ -62,6 +62,6 @@ func ApplicationInsightsCore(cfg *AppInsightsConfig) (zapcore.Core, func(), erro core := zapai.NewCore(cfg.level, sink) core = core.WithFieldMappers(zapai.DefaultMappers) // add normalized fields for the built-in AI Tags - // TODO(rbtr): move to the caller + return core.With(cfg.Fields), aiclose, nil } diff --git a/cns/logger/v2/cores/etw_windows.go b/cns/logger/v2/cores/etw_windows.go index 51e6656f48..da0d49b672 100644 --- a/cns/logger/v2/cores/etw_windows.go +++ b/cns/logger/v2/cores/etw_windows.go @@ -1,15 +1,41 @@ package logger import ( + "encoding/json" + "github.com/Azure/azure-container-networking/zapetw" + "github.com/pkg/errors" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) type ETWConfig struct { - EventName string - Level zapcore.Level - ProviderName string + EventName string `json:"eventname"` + Level string `json:"level"` + level zapcore.Level `json:"-"` + ProviderName string `json:"providername"` + Fields []zapcore.Field `json:"fields"` +} + +// UnmarshalJSON implements json.Unmarshaler for the Config. +// It only differs from the default by parsing the +// Level string into a zapcore.Level and setting the level field. +func (cfg *ETWConfig) UnmarshalJSON(data []byte) error { + type Alias ETWConfig + aux := &struct { + *Alias + }{ + Alias: (*Alias)(cfg), + } + if err := json.Unmarshal(data, &aux); err != nil { + return errors.Wrap(err, "failed to unmarshal ETWConfig") + } + lvl, err := zapcore.ParseLevel(cfg.Level) + if err != nil { + return errors.Wrap(err, "failed to parse ETWConfig Level") + } + cfg.level = lvl + return nil } // ETWCore builds a zapcore.Core that sends logs to ETW. @@ -18,5 +44,5 @@ func ETWCore(cfg *ETWConfig) (zapcore.Core, func(), error) { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder jsonEncoder := zapcore.NewJSONEncoder(encoderConfig) - return zapetw.New(cfg.ProviderName, cfg.EventName, jsonEncoder, cfg.Level) //nolint:wrapcheck // ignore + return zapetw.New(cfg.ProviderName, cfg.EventName, jsonEncoder, cfg.level) //nolint:wrapcheck // ignore } diff --git a/cns/logger/v2/cores/file.go b/cns/logger/v2/cores/file.go index 21b6b1182a..7aa01bb8e7 100644 --- a/cns/logger/v2/cores/file.go +++ b/cns/logger/v2/cores/file.go @@ -10,11 +10,12 @@ import ( ) type FileConfig struct { - Filepath string `json:"filepath"` - Level string `json:"level"` - level zapcore.Level `json:"-"` - MaxBackups int `json:"maxBackups"` - MaxSize int `json:"maxSize"` + Filepath string `json:"filepath"` + Level string `json:"level"` + level zapcore.Level `json:"-"` + MaxBackups int `json:"maxBackups"` + MaxSize int `json:"maxSize"` + Fields []zapcore.Field `json:"fields"` } // UnmarshalJSON implements json.Unmarshaler for the Config. diff --git a/cns/logger/v2/cores/stdout.go b/cns/logger/v2/cores/stdout.go index 93ad09767a..9882483690 100644 --- a/cns/logger/v2/cores/stdout.go +++ b/cns/logger/v2/cores/stdout.go @@ -1,13 +1,42 @@ package logger import ( + "encoding/json" "os" logfmt "github.com/jsternberg/zap-logfmt" + "github.com/pkg/errors" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) +type StdoutConfig struct { + Level string `json:"level"` + level zapcore.Level `json:"-"` + Fields []zapcore.Field `json:"fields"` +} + +// UnmarshalJSON implements json.Unmarshaler for the Config. +// It only differs from the default by parsing the +// Level string into a zapcore.Level and setting the level field. +func (cfg *StdoutConfig) UnmarshalJSON(data []byte) error { + type Alias StdoutConfig + aux := &struct { + *Alias + }{ + Alias: (*Alias)(cfg), + } + if err := json.Unmarshal(data, &aux); err != nil { + return errors.Wrap(err, "failed to unmarshal StdoutConfig") + } + lvl, err := zapcore.ParseLevel(cfg.Level) + if err != nil { + return errors.Wrap(err, "failed to parse StdoutConfig Level") + } + cfg.level = lvl + return nil +} + // StdoutCore builds a zapcore.Core that writes to stdout. func StdoutCore(l zapcore.Level) zapcore.Core { encoderConfig := zap.NewProductionEncoderConfig() diff --git a/cns/logger/v2/fields.go b/cns/logger/v2/fields.go new file mode 100644 index 0000000000..926ac0720f --- /dev/null +++ b/cns/logger/v2/fields.go @@ -0,0 +1,23 @@ +package logger + +import ( + "github.com/Azure/azure-container-networking/common" + "go.uber.org/zap" +) + +// MetadataToFields transforms Az IMDS Metadata in to zap.Field for +// attaching to a root zap core or logger instance. +// This uses the nice-names from the zapai.DefaultMappers instead of +// raw AppInsights key names. +func MetadataToFields(meta common.Metadata) []zap.Field { + return []zap.Field{ + zap.String("account", meta.SubscriptionID), + zap.String("anonymous_user_id", meta.VMName), + zap.String("location", meta.Location), + zap.String("resource_group", meta.ResourceGroupName), + zap.String("vm_size", meta.VMSize), + zap.String("os_version", meta.OSVersion), + zap.String("vm_id", meta.VMID), + zap.String("session_id", meta.VMID), + } +} diff --git a/cns/logger/v2/logger.go b/cns/logger/v2/logger.go index 54863c7620..f5c5c3a85b 100644 --- a/cns/logger/v2/logger.go +++ b/cns/logger/v2/logger.go @@ -16,6 +16,7 @@ func (c compoundCloser) Close() { } } +// New creates a v2 CNS logger built with Zap. func New(cfg *Config) (*zap.Logger, func(), error) { cfg.Normalize() core := cores.StdoutCore(cfg.level) diff --git a/cns/logger/v2/logger_linux.go b/cns/logger/v2/logger_linux.go index c875f3faaf..e0311284fc 100644 --- a/cns/logger/v2/logger_linux.go +++ b/cns/logger/v2/logger_linux.go @@ -4,7 +4,7 @@ import ( "go.uber.org/zap/zapcore" ) -// platformCore returns a no-op core for Linux. +// On Linux, platformCore returns a no-op core. func platformCore(*Config) (zapcore.Core, func(), error) { return zapcore.NewNopCore(), func() {}, nil } diff --git a/cns/logger/v2/logger_windows.go b/cns/logger/v2/logger_windows.go index e6e5f198dc..186fae1f14 100644 --- a/cns/logger/v2/logger_windows.go +++ b/cns/logger/v2/logger_windows.go @@ -5,7 +5,7 @@ import ( "go.uber.org/zap/zapcore" ) -// platformCore returns a zapcore.Core that sends logs to ETW. +// On Windows, platformCore returns a zapcore.Core that sends logs to ETW. func platformCore(cfg *Config) (zapcore.Core, func(), error) { if cfg.ETW == nil { return zapcore.NewNopCore(), func() {}, nil diff --git a/cns/service/main.go b/cns/service/main.go index 77c1e42bc1..e5348da028 100644 --- a/cns/service/main.go +++ b/cns/service/main.go @@ -628,6 +628,14 @@ func main() { } } + // Get host metadata and attach it to logger(v2) appinsights config. + // If this errors, we will not have metadata in the AI logs. Should we exit? + metadata, _ := acn.GetHostMetadata(aitelemetry.MetadataFile) + aifields := loggerv2.MetadataToFields(metadata) + if cnsconfig.Logger.AppInsights != nil { + cnsconfig.Logger.AppInsights.Fields = append(cnsconfig.Logger.AppInsights.Fields, aifields...) + } + // build the zap logger z, c, err := loggerv2.New(&cnsconfig.Logger) defer c() @@ -635,9 +643,11 @@ func main() { fmt.Printf("failed to create logger: %v", err) os.Exit(1) } - // set the logger to the global logger if configured + host, _ := os.Hostname() + z = z.With(zap.String("hostname", host), zap.String("version", version), zap.String("kubernetes_apiserver", os.Getenv("KUBERNETES_SERVICE_HOST"))) + // Set the v2 logger to the global logger if v2 logger enabled. if cnsconfig.EnableLoggerV2 { - logger.Printf("hotswapping logger v2") + logger.Printf("hotswapping logger v2") //nolint:staticcheck // ignore new deprecation logger.Log = loggerv2.AsV1(z, c) } @@ -1373,6 +1383,8 @@ func reconcileInitialCNSState(ctx context.Context, cli nodeNetworkConfigGetter, } // InitializeCRDState builds and starts the CRD controllers. +// +//nolint:gocyclo // legacy func InitializeCRDState(ctx context.Context, z *zap.Logger, httpRestService cns.HTTPService, cnsconfig *configuration.CNSConfig) error { // convert interface type to implementation type httpRestServiceImplementation, ok := httpRestService.(*restserver.HTTPRestService)