Skip to content

Commit

Permalink
ETW logging implementation in CNI (#2668)
Browse files Browse the repository at this point in the history
* Add ETW support in zap logger for CNI

Added a zap WriteSyncer to enable direct ETW logging through zap core, maintaining existing logging structure while to ETW from CNI.

* Transform 'zapetw' package into a standalone module for external use

* Revert "Transform 'zapetw' package into a standalone module for external use"

This reverts commit 63050ed.

* Relocate EtwWriteSyncer.go to zapetw module for improved organization

- Renamed and moved cni/log/ETWZapCore/EtwWriteSyncer.go to zapetw/write_syncer.go.

* Applied gofumpt formatting to adhere to style guidelines.

* 1. Implemented platform-specific ETW logging enhancements.
2. Refactor ETW initialization into dedicated method and zapetw package.

* Changed InitETWLogger method signature for Linux.

* Wrapped error messages at each level of the call hierarchy.

* Removed punctuation marks from error messages.

* Wrapped error messages with errors.wrap method.

* Added comments for clarity.

* implemented zap.core for ETW.

* Fixed lint issues.

* Catch errors from etw.writeEvent method.

* Renamed provider.

* Abstracted etw core creation in logger_windows.
Removed unsupported error from logger_linux to keep the behaviour uniform.

* renamed unused parameter.

* Renamed variable to lower camel case as it is private. Removed additional local reference.

* fixed variable name.

* Added comment.

* Renamed ETW provider, removed application names from the provider name.

---------

Co-authored-by: Sivakami Subramaniam <t-sivakamis@microsoft.com>
  • Loading branch information
sivakami-projects and Sivakami Subramaniam authored Apr 18, 2024
1 parent 1f01781 commit 1eed84e
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 6 deletions.
18 changes: 12 additions & 6 deletions cni/log/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ var (
const (
maxLogFileSizeInMb = 5
maxLogFileCount = 8
etwCNIEventName = "Azure-CNI"
loggingLevel = zapcore.DebugLevel
)

func initZapLog(logFile string) *zap.Logger {
Expand All @@ -30,13 +32,17 @@ func initZapLog(logFile string) *zap.Logger {
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
jsonEncoder := zapcore.NewJSONEncoder(encoderConfig)

core := zapcore.NewCore(jsonEncoder, logFileCNIWriter, zapcore.DebugLevel)
Logger := zap.New(core)
return Logger
textFileCore := zapcore.NewCore(jsonEncoder, logFileCNIWriter, loggingLevel)
core, err := JoinPlatformCores(textFileCore, loggingLevel)
if err != nil {
// If we fail to join the platform cores, fallback to the original core.
core = textFileCore
}
return zap.New(core, zap.AddCaller()).With(zap.Int("pid", os.Getpid()))
}

var (
CNILogger = initZapLog(zapCNILogFile).With(zap.Int("pid", os.Getpid()))
IPamLogger = initZapLog(zapIpamLogFile).With(zap.Int("pid", os.Getpid()))
TelemetryLogger = initZapLog(zapTelemetryLogFile).With(zap.Int("pid", os.Getpid()))
CNILogger = initZapLog(zapCNILogFile)
IPamLogger = initZapLog(zapIpamLogFile)
TelemetryLogger = initZapLog(zapTelemetryLogFile)
)
8 changes: 8 additions & 0 deletions cni/log/logger_linux.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package log

import (
"go.uber.org/zap/zapcore"
)

const (
// LogPath is the path where log files are stored.
LogPath = "/var/log/"
)

func JoinPlatformCores(c zapcore.Core, _ zapcore.Level) (zapcore.Core, error) {
return c, nil
}
28 changes: 28 additions & 0 deletions cni/log/logger_windows.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
package log

import (
"github.com/Azure/azure-container-networking/zapetw"
"github.com/pkg/errors"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

const (
// LogPath is the path where log files are stored.
LogPath = ""
)

func JoinPlatformCores(core zapcore.Core, loggingLevel zapcore.Level) (zapcore.Core, error) {
etwcore, err := etwCore(loggingLevel)
if err != nil {
return core, err
}
teecore := zapcore.NewTee(core, etwcore)
return teecore, nil
}

func etwCore(loggingLevel zapcore.Level) (zapcore.Core, error) {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
jsonEncoder := zapcore.NewJSONEncoder(encoderConfig)

etwcore, err := zapetw.NewETWCore(etwCNIEventName, jsonEncoder, loggingLevel)
if err != nil {
return nil, errors.Wrap(err, "failed to create ETW core")
}
return etwcore, nil
}
92 changes: 92 additions & 0 deletions zapetw/core_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package zapetw

import (
"github.com/Microsoft/go-winio/pkg/etw"
"github.com/pkg/errors"
"go.uber.org/zap/zapcore"
)

// <product_name>-<component_name>
const providername = "ACN-Monitoring"

type ETWCore struct {
provider *etw.Provider
eventName string
encoder zapcore.Encoder
fields []zapcore.Field
zapcore.LevelEnabler
}

func NewETWCore(eventName string, encoder zapcore.Encoder, levelEnabler zapcore.LevelEnabler) (*ETWCore, error) {
provider, err := etw.NewProviderWithOptions(providername)
if err != nil {
return nil, errors.Wrap(err, "failed to create ETW provider")
}
return &ETWCore{
provider: provider,
eventName: eventName,
encoder: encoder,
LevelEnabler: levelEnabler,
}, nil
}

func (core *ETWCore) With(fields []zapcore.Field) zapcore.Core {
return &ETWCore{
provider: core.provider,
eventName: core.eventName,
encoder: core.encoder,
LevelEnabler: core.LevelEnabler,
fields: append(core.fields, fields...),
}
}

// Check is an implementation of the zapcore.Core interface's Check method.
// Check determines whether the logger core is enabled at the supplied zapcore.Entry's Level.
// If enabled, it adds the core to the CheckedEntry and returns it, otherwise returns the CheckedEntry unchanged.
func (core *ETWCore) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if core.Enabled(entry.Level) {
return checkedEntry.AddCore(entry, core)
}
return checkedEntry
}

func (core *ETWCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
etwLevel := zapLevelToETWLevel(entry.Level)

buffer, err := core.encoder.EncodeEntry(entry, fields)
if err != nil {
return errors.Wrap(err, "failed to encode entry")
}

err = core.provider.WriteEvent(
core.eventName,
[]etw.EventOpt{etw.WithLevel(etwLevel)},
[]etw.FieldOpt{etw.StringField("Message", buffer.String())},
)
if err != nil {
return errors.Wrap(err, "failed to write event")
}

return nil
}

func (core *ETWCore) Sync() error {
return nil
}

func zapLevelToETWLevel(level zapcore.Level) etw.Level {
switch level {
case zapcore.DebugLevel:
return etw.LevelVerbose // ETW doesn't have a Debug level, so Verbose is used instead.
case zapcore.InfoLevel:
return etw.LevelInfo
case zapcore.WarnLevel:
return etw.LevelWarning
case zapcore.ErrorLevel:
return etw.LevelError
case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel, zapcore.InvalidLevel:
return etw.LevelCritical
default:
return etw.LevelAlways
}
}

0 comments on commit 1eed84e

Please sign in to comment.